diff options
author | Henrik Paul <henrik@vaadin.com> | 2015-03-05 10:00:33 +0200 |
---|---|---|
committer | Henrik Paul <henrik@vaadin.com> | 2015-03-05 10:00:33 +0200 |
commit | 44c874331e789b9cd09f7132ae5710736141386a (patch) | |
tree | 2f8d048cd036c8c59151d45c04fcede693b99118 /client/src | |
parent | dc760a8a2cd74642c5e188ad7f890cb43047a6ad (diff) | |
parent | cdb8996e925784c908b8b7607b1650c09811f293 (diff) | |
download | vaadin-framework-44c874331e789b9cd09f7132ae5710736141386a.tar.gz vaadin-framework-44c874331e789b9cd09f7132ae5710736141386a.zip |
Merge branch 'master' into HEAD
Change-Id: I85867836aba8bd2cb51729fecdb1c449c439d3e7
Diffstat (limited to 'client/src')
-rw-r--r-- | client/src/com/vaadin/client/widget/escalator/FlyweightRow.java | 1 | ||||
-rw-r--r-- | client/src/com/vaadin/client/widgets/Escalator.java | 192 | ||||
-rw-r--r-- | client/src/com/vaadin/client/widgets/Grid.java | 178 |
3 files changed, 253 insertions, 118 deletions
diff --git a/client/src/com/vaadin/client/widget/escalator/FlyweightRow.java b/client/src/com/vaadin/client/widget/escalator/FlyweightRow.java index 6e25e82235..8628adb05f 100644 --- a/client/src/com/vaadin/client/widget/escalator/FlyweightRow.java +++ b/client/src/com/vaadin/client/widget/escalator/FlyweightRow.java @@ -21,7 +21,6 @@ import java.util.Iterator; import java.util.List; import com.google.gwt.dom.client.TableRowElement; -import com.vaadin.client.widgets.Escalator; /** * An internal implementation of the {@link Row} interface. diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index cc665b9b3f..efa777111a 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -826,18 +826,23 @@ public class Escalator extends Widget implements RequiresResize, double tableWrapperWidth = widthOfEscalator; boolean verticalScrollNeeded = scrollContentHeight > tableWrapperHeight - - header.heightOfSection - footer.heightOfSection; - boolean horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth; + + WidgetUtil.PIXEL_EPSILON + - header.heightOfSection + - footer.heightOfSection; + boolean horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth + + WidgetUtil.PIXEL_EPSILON; // One dimension got scrollbars, but not the other. Recheck time! if (verticalScrollNeeded != horizontalScrollNeeded) { if (!verticalScrollNeeded && horizontalScrollNeeded) { verticalScrollNeeded = scrollContentHeight > tableWrapperHeight + + WidgetUtil.PIXEL_EPSILON - header.heightOfSection - footer.heightOfSection - horizontalScrollbar.getScrollbarThickness(); } else { horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth + + WidgetUtil.PIXEL_EPSILON - verticalScrollbar.getScrollbarThickness(); } } @@ -1823,12 +1828,13 @@ public class Escalator extends Widget implements RequiresResize, */ protected void reapplyRowWidths() { double rowWidth = columnConfiguration.calculateRowWidth(); + if (rowWidth < 0) { + return; + } - com.google.gwt.dom.client.Element row = root.getFirstChildElement(); + Element row = root.getFirstChildElement(); while (row != null) { - if (rowWidth >= 0) { - row.getStyle().setWidth(rowWidth, Unit.PX); - } + row.getStyle().setWidth(rowWidth, Unit.PX); row = row.getNextSiblingElement(); } } @@ -2057,22 +2063,17 @@ public class Escalator extends Widget implements RequiresResize, cellClone.getStyle().clearWidth(); rowElement.insertBefore(cellClone, cellOriginal); - - /* - * [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME: not - * using the double-version is a workaround for a bug. It'll be - * converted to use the double version at a later time - */ double requiredWidth = WidgetUtil - .getRequiredWidthBoundingClientRect(cellClone); + .getRequiredWidthBoundingClientRectDouble(cellClone); - if (BrowserInfo.get().isIE9()) { + if (BrowserInfo.get().isIE()) { /* - * IE9 does not support subpixels. Usually it is rounded - * down which leads to content not shown. Increase the - * counted required size by one just to be on the safe side. + * IE browsers have some issues with subpixels. Occasionally + * content is overflown even if not necessary. Increase the + * counted required size by 0.01 just to be on the safe + * side. */ - requiredWidth += 1; + requiredWidth += 0.01; } maxCellWidth = Math.max(requiredWidth, maxCellWidth); @@ -3900,7 +3901,7 @@ public class Escalator extends Widget implements RequiresResize, } else { /* * the column's width is calculated at Escalator.onLoad - * via measureIfNeeded! + * via measureAndSetWidthIfNeeded! */ measuringRequested = true; } @@ -3926,7 +3927,7 @@ public class Escalator extends Widget implements RequiresResize, * widths yet. * * This is fixed during Escalator.onLoad, by the call to - * "measureIfNeeded", which fixes "everything". + * "measureAndSetWidthIfNeeded", which fixes "everything". */ if (!measuringRequested) { return calculatedWidth; @@ -3941,7 +3942,7 @@ public class Escalator extends Widget implements RequiresResize, * Called by {@link Escalator#onLoad()}. */ public boolean measureAndSetWidthIfNeeded() { - assert isAttached() : "Column.measureIfNeeded() was called even though Escalator was not attached!"; + assert isAttached() : "Column.measureAndSetWidthIfNeeded() was called even though Escalator was not attached!"; if (measuringRequested) { measuringRequested = false; @@ -3959,6 +3960,11 @@ public class Escalator extends Widget implements RequiresResize, private final List<Column> columns = new ArrayList<Column>(); private int frozenColumns = 0; + /* + * TODO: this is a bit of a duplicate functionality with the + * Column.calculatedWidth caching. Probably should use one or the other, + * not both + */ /** * A cached array of all the calculated column widths. * @@ -4103,6 +4109,8 @@ public class Escalator extends Widget implements RequiresResize, */ @Override public void insertColumns(final int index, final int numberOfColumns) { + subpixelBrowserBugDetector.invalidateFix(); + // Validate if (index < 0 || index > getColumnCount()) { throw new IndexOutOfBoundsException("The given index(" + index @@ -4254,14 +4262,23 @@ public class Escalator extends Widget implements RequiresResize, for (Entry<Integer, Double> entry : indexWidthMap.entrySet()) { int index = entry.getKey().intValue(); double width = entry.getValue().doubleValue(); + + if (index == getColumnCount() - 1) { + subpixelBrowserBugDetector.invalidateFix(); + } + checkValidColumnIndex(index); columns.get(index).setWidth(width); + } widthsArray = null; header.reapplyColumnWidths(); body.reapplyColumnWidths(); footer.reapplyColumnWidths(); + + subpixelBrowserBugDetector.checkAndFix(); + recalculateElementSizes(); } @@ -4356,6 +4373,137 @@ public class Escalator extends Widget implements RequiresResize, } } + private class SubpixelBrowserBugDetector { + private static final double SUBPIXEL_ADJUSTMENT = .1; + private boolean hasAlreadyBeenFixed = false; + + /** + * This is a fix essentially for Firefox and how it handles subpixels. + * <p> + * Even if an element has {@code style="width: 1000.12px"}, the bounding + * box's width in Firefox is usually nothing of that sort. It's actually + * 1000.11669921875 (in version 35.0.1). That's not even close, when + * talking about floating point precision. Other browsers handle the + * subpixels way better + * <p> + * In any case, we need to fix that. And that's fixed by simply checking + * if the sum of the width of all the cells is larger than the width of + * the row. If it is, we <i>hack</i> the last column + * {@value #SUBPIXEL_ADJUSTMENT}px narrower. + */ + public void checkAndFix() { + if (!hasAlreadyBeenFixed && hasSubpixelBrowserBug()) { + fixSubpixelBrowserBug(); + hasAlreadyBeenFixed = true; + } + } + + public void invalidateFix() { + adjustBookkeepingPixels(SUBPIXEL_ADJUSTMENT); + hasAlreadyBeenFixed = false; + } + + private boolean hasSubpixelBrowserBug() { + final RowContainer rowContainer; + if (header.getRowCount() > 0) { + rowContainer = header; + } else if (body.getRowCount() > 0) { + rowContainer = body; + } else if (footer.getRowCount() > 0) { + rowContainer = footer; + } else { + return false; + } + + double sumOfCellWidths = 0; + TableRowElement tr = rowContainer.getElement().getRows().getItem(0); + + if (tr == null) { + /* + * for some weird reason, the row might be null at this point in + * (some?) webkit browsers. + */ + return false; + } + + NodeList<TableCellElement> cells = tr.getCells(); + assert cells != null : "cells was null, why is it null?"; + + for (int i = 0; i < cells.getLength(); i++) { + TableCellElement cell = cells.getItem(i); + if (!cell.getStyle().getDisplay() + .equals(Display.NONE.getCssName())) { + sumOfCellWidths += WidgetUtil.getBoundingClientRect(cell) + .getWidth(); + } + } + + double rowWidth = WidgetUtil.getBoundingClientRect(tr).getWidth(); + return sumOfCellWidths >= rowWidth; + } + + private void fixSubpixelBrowserBug() { + assert columnConfiguration.getColumnCount() > 0 : "Why are we running this code if there are no columns?"; + + adjustBookkeepingPixels(-SUBPIXEL_ADJUSTMENT); + + fixSubpixelBrowserBugFor(header); + fixSubpixelBrowserBugFor(body); + fixSubpixelBrowserBugFor(footer); + } + + private void adjustBookkeepingPixels(double adjustment) { + int lastColumnIndex = columnConfiguration.columns.size() - 1; + if (lastColumnIndex < 0) { + return; + } + + columnConfiguration.columns.get(lastColumnIndex).calculatedWidth += adjustment; + if (columnConfiguration.widthsArray != null) { + columnConfiguration.widthsArray[lastColumnIndex] += adjustment; + } + } + + /** + * Adjust the last non-spanned cell by {@link #SUBPIXEL_ADJUSTMENT} ( + * {@value #SUBPIXEL_ADJUSTMENT}px). + * <p> + * We'll do this brute-force, by individually measuring and shrinking + * the last non-spanned cell. Brute-force, since each row might be + * spanned differently - we can't simply pick one index and one width, + * and mass-apply that to everything :( + */ + private void fixSubpixelBrowserBugFor(RowContainer rowContainer) { + if (rowContainer.getRowCount() == 0) { + return; + } + + NodeList<TableRowElement> rows = rowContainer.getElement() + .getRows(); + for (int i = 0; i < rows.getLength(); i++) { + + NodeList<TableCellElement> cells = rows.getItem(i).getCells(); + TableCellElement lastNonspannedCell = null; + for (int j = cells.getLength() - 1; j >= 0; j--) { + TableCellElement cell = cells.getItem(j); + if (!cell.getStyle().getDisplay() + .equals(Display.NONE.getCssName())) { + lastNonspannedCell = cell; + break; + } + } + + assert lastNonspannedCell != null : "all cells were \"display: none\" on row " + + i + " in " + rowContainer.getElement().getTagName(); + + double cellWidth = WidgetUtil.getBoundingClientRect( + lastNonspannedCell).getWidth(); + double newWidth = cellWidth - SUBPIXEL_ADJUSTMENT; + lastNonspannedCell.getStyle().setWidth(newWidth, Unit.PX); + } + } + } + /** * A decision on how to measure a spacer when it is partially within a * designated range. @@ -5222,6 +5370,8 @@ public class Escalator extends Widget implements RequiresResize, } }; + private final SubpixelBrowserBugDetector subpixelBrowserBugDetector = new SubpixelBrowserBugDetector(); + private final ElementPositionBookkeeper positions = new ElementPositionBookkeeper(); /** diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 0edcaa9c91..2725f84fe6 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -61,6 +61,7 @@ import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.ResizeComposite; import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.BrowserInfo; import com.vaadin.client.DeferredWorker; import com.vaadin.client.WidgetUtil; import com.vaadin.client.data.DataChangeHandler; @@ -2454,29 +2455,6 @@ public class Grid<T> extends ResizeComposite implements applyColumnWidths(); } else { applyColumnWidthsWithExpansion(); - - /* - * [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME: just - * dump all the remaining pixels into the last column and - * whistle loudly - */ - boolean dumpIntoLastColumn = false; - double escalatorWidth = escalator.getInnerWidth(); - double occupiedWidth = 0; - for (Column column : getColumns()) { - occupiedWidth += column.getWidthActual(); - if (column.getWidth() < 0 && column.getExpandRatio() != 0) { - dumpIntoLastColumn = true; - } - } - - if (dumpIntoLastColumn) { - Column<?, T> lastColumn = getColumn(getColumnCount() - 1); - double width = Math.floor(lastColumn.getWidthActual() - + (escalatorWidth - occupiedWidth)); - escalator.getColumnConfiguration().setColumnWidth( - getColumnCount() - 1, width); - } } } @@ -2533,10 +2511,12 @@ public class Grid<T> extends ResizeComposite implements } private void applyColumnWidthsWithExpansion() { - boolean someColumnExpands = false; + boolean defaultExpandRatios = true; int totalRatios = 0; double reservedPixels = 0; - final Set<Column<?, ?>> columnsToExpand = new HashSet<Column<?, ?>>(); + final Set<Column<?, T>> columnsToExpand = new HashSet<Column<?, T>>(); + List<Column<?, T>> nonFixedColumns = new ArrayList<Column<?, T>>(); + Map<Integer, Double> columnSizes = new HashMap<Integer, Double>(); /* * Set all fixed widths and also calculate the size-to-fit widths @@ -2545,49 +2525,37 @@ public class Grid<T> extends ResizeComposite implements * This way we know with how many pixels we have left to expand the * rest. */ - for (Column<?, ?> column : getColumns()) { + for (Column<?, T> column : getColumns()) { final double widthAsIs = column.getWidth(); final boolean isFixedWidth = widthAsIs >= 0; final double widthFixed = Math.max(widthAsIs, column.getMinimumWidth()); - final int expandRatio = column.getExpandRatio(); + defaultExpandRatios = defaultExpandRatios + && column.getExpandRatio() == -1; if (isFixedWidth) { - column.doSetWidth(widthFixed); + columnSizes.put(indexOfColumn(column), widthFixed); + reservedPixels += widthFixed; } else { - column.doSetWidth(-1); - final double newWidth = column.getWidthActual(); - final double maxWidth = getMaxWidth(column); - boolean shouldExpand = newWidth < maxWidth - && expandRatio > 0; - if (shouldExpand) { - totalRatios += expandRatio; - columnsToExpand.add(column); - someColumnExpands = true; - } + nonFixedColumns.add(column); + columnSizes.put(indexOfColumn(column), -1.0d); } - reservedPixels += column.getWidthActual(); } - /* - * If no column has a positive expand ratio, all columns with a - * negative expand ratio has an expand ratio. Columns with 0 expand - * ratio are excluded. - * - * This means that if we only define one column to have 0 expand, it - * will be the only one not to expand, while all the others expand. - */ - if (!someColumnExpands) { - assert totalRatios == 0 : "totalRatios should've been 0"; - assert columnsToExpand.isEmpty() : "columnsToExpand should've been empty"; - for (Column<?, ?> column : getColumns()) { - final double width = column.getWidth(); - final int expandRatio = column.getExpandRatio(); - if (width < 0 && expandRatio < 0) { - totalRatios++; - columnsToExpand.add(column); - } + setColumnSizes(columnSizes); + + for (Column<?, T> column : nonFixedColumns) { + final int expandRatio = (defaultExpandRatios ? 1 : column + .getExpandRatio()); + final double newWidth = column.getWidthActual(); + final double maxWidth = getMaxWidth(column); + boolean shouldExpand = newWidth < maxWidth && expandRatio > 0; + if (shouldExpand) { + totalRatios += expandRatio; + columnsToExpand.add(column); } + reservedPixels += newWidth; + columnSizes.put(indexOfColumn(column), newWidth); } /* @@ -2595,8 +2563,7 @@ public class Grid<T> extends ResizeComposite implements * can distribute the remaining pixels to all columns according to * their expand ratios. */ - // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME: ceil - double pixelsToDistribute = Math.ceil(escalator.getInnerWidth()) + double pixelsToDistribute = escalator.getInnerWidth() - reservedPixels; if (pixelsToDistribute <= 0 || totalRatios <= 0) { return; @@ -2610,30 +2577,30 @@ public class Grid<T> extends ResizeComposite implements boolean aColumnHasMaxedOut; do { aColumnHasMaxedOut = false; - // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME floor - final double widthPerRatio = Math.floor(pixelsToDistribute - / totalRatios); - final Iterator<Column<?, ?>> i = columnsToExpand.iterator(); + final double widthPerRatio = pixelsToDistribute / totalRatios; + final Iterator<Column<?, T>> i = columnsToExpand.iterator(); while (i.hasNext()) { - final Column<?, ?> column = i.next(); + final Column<?, T> column = i.next(); final int expandRatio = getExpandRatio(column, - someColumnExpands); - final double autoWidth = column.getWidthActual(); + defaultExpandRatios); + final double autoWidth = columnSizes + .get(indexOfColumn(column)); final double maxWidth = getMaxWidth(column); - final double widthCandidate = autoWidth + widthPerRatio + double expandedWidth = autoWidth + widthPerRatio * expandRatio; - if (maxWidth <= widthCandidate) { - column.doSetWidth(maxWidth); - totalRatios -= expandRatio; - pixelsToDistribute -= maxWidth - autoWidth; + if (maxWidth <= expandedWidth) { i.remove(); + totalRatios -= expandRatio; aColumnHasMaxedOut = true; + pixelsToDistribute -= maxWidth - autoWidth; + columnSizes.put(indexOfColumn(column), maxWidth); } } } while (aColumnHasMaxedOut); if (totalRatios <= 0 && columnsToExpand.isEmpty()) { + setColumnSizes(columnSizes); return; } assert pixelsToDistribute > 0 : "We've run out of pixels to distribute (" @@ -2648,16 +2615,28 @@ public class Grid<T> extends ResizeComposite implements * If we still have anything left, distribute the remaining pixels * to the remaining columns. */ - // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME: floor - final double widthPerRatio = Math.floor(pixelsToDistribute - / totalRatios); - for (Column<?, ?> column : columnsToExpand) { + final double widthPerRatio; + int leftOver = 0; + if (BrowserInfo.get().isIE8() || BrowserInfo.get().isIE9() + || BrowserInfo.getBrowserString().contains("PhantomJS")) { + // These browsers report subpixels as integers. this usually + // results into issues.. + widthPerRatio = (int) (pixelsToDistribute / totalRatios); + leftOver = (int) (pixelsToDistribute - widthPerRatio + * totalRatios); + } else { + widthPerRatio = pixelsToDistribute / totalRatios; + } + for (Column<?, T> column : columnsToExpand) { final int expandRatio = getExpandRatio(column, - someColumnExpands); - final double autoWidth = column.getWidthActual(); - final double totalWidth = autoWidth + widthPerRatio - * expandRatio; - column.doSetWidth(totalWidth); + defaultExpandRatios); + final double autoWidth = columnSizes.get(indexOfColumn(column)); + double totalWidth = autoWidth + widthPerRatio * expandRatio; + if (leftOver > 0) { + totalWidth += 1; + leftOver--; + } + columnSizes.put(indexOfColumn(column), totalWidth); totalRatios -= expandRatio; } @@ -2686,12 +2665,12 @@ public class Grid<T> extends ResizeComposite implements * wouldn't show up in that set. */ - // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME ceil - double minWidth = Math.ceil(getMinWidth(column)); - double currentWidth = column.getWidthActual(); + double minWidth = getMinWidth(column); + double currentWidth = columnSizes + .get(indexOfColumn(column)); boolean hasAutoWidth = column.getWidth() < 0; if (hasAutoWidth && currentWidth < minWidth) { - column.doSetWidth(minWidth); + columnSizes.put(indexOfColumn(column), minWidth); pixelsToRemoveFromOtherColumns += (minWidth - currentWidth); minWidthsCausedReflows = true; @@ -2710,27 +2689,36 @@ public class Grid<T> extends ResizeComposite implements */ totalRatios = 0; for (Column<?, ?> column : columnsToExpand) { - totalRatios += getExpandRatio(column, someColumnExpands); + totalRatios += getExpandRatio(column, defaultExpandRatios); } - // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME: ceil - final double pixelsToRemovePerRatio = Math - .ceil(pixelsToRemoveFromOtherColumns / totalRatios); - for (Column<?, ?> column : columnsToExpand) { + final double pixelsToRemovePerRatio = pixelsToRemoveFromOtherColumns + / totalRatios; + for (Column<?, T> column : columnsToExpand) { final double pixelsToRemove = pixelsToRemovePerRatio - * getExpandRatio(column, someColumnExpands); - column.doSetWidth(column.getWidthActual() - pixelsToRemove); + * getExpandRatio(column, defaultExpandRatios); + int colIndex = indexOfColumn(column); + columnSizes.put(colIndex, columnSizes.get(colIndex) + - pixelsToRemove); } } while (minWidthsCausedReflows); + + // Finally set all the column sizes. + setColumnSizes(columnSizes); + } + + private void setColumnSizes(Map<Integer, Double> columnSizes) { + // Set all widths at once + escalator.getColumnConfiguration().setColumnWidths(columnSizes); } private int getExpandRatio(Column<?, ?> column, - boolean someColumnExpands) { + boolean defaultExpandRatios) { int expandRatio = column.getExpandRatio(); if (expandRatio > 0) { return expandRatio; } else if (expandRatio < 0) { - assert !someColumnExpands : "No columns should've expanded"; + assert defaultExpandRatios : "No columns should've expanded"; return 1; } else { assert false : "this method should've not been called at all if expandRatio is 0"; @@ -6220,9 +6208,7 @@ public class Grid<T> extends ResizeComposite implements @Override public void execute() { - if (!autoColumnWidthsRecalculator.isScheduled()) { - autoColumnWidthsRecalculator.schedule(); - } + recalculateColumnWidths(); } }); } |