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.
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();
}
}
*/
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();
}
}
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);
} else {
/*
* the column's width is calculated at Escalator.onLoad
- * via measureIfNeeded!
+ * via measureAndSetWidthIfNeeded!
*/
measuringRequested = true;
}
* widths yet.
*
* This is fixed during Escalator.onLoad, by the call to
- * "measureIfNeeded", which fixes "everything".
+ * "measureAndSetWidthIfNeeded", which fixes "everything".
*/
if (!measuringRequested) {
return calculatedWidth;
* 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;
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.
*
*/
@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
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();
}
}
}
+ 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);
+ }
+ }
+ }
+
// abs(atan(y/x))*(180/PI) = n deg, x = 1, solve y
/**
* The solution to
}
};
+ private final SubpixelBrowserBugDetector subpixelBrowserBugDetector = new SubpixelBrowserBugDetector();
+
/**
* Creates a new Escalator widget instance.
*/
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;
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);
- }
}
}
}
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
* 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);
}
/*
* 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;
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 ("
* 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;
}
* 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;
*/
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";
@Override
public void execute() {
- if (!autoColumnWidthsRecalculator.isScheduled()) {
- autoColumnWidthsRecalculator.schedule();
- }
+ recalculateColumnWidths();
}
});
}
package com.vaadin.tests.components.grid;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
import org.junit.Test;
+import org.openqa.selenium.remote.DesiredCapabilities;
import com.vaadin.testbench.elements.ButtonElement;
import com.vaadin.testbench.elements.GridElement;
+import com.vaadin.testbench.parallel.BrowserUtil;
import com.vaadin.tests.tb3.MultiBrowserTest;
public class GridWidthIncreaseTest extends MultiBrowserTest {
private static int INCREASE_COUNT = 3;
@Test
- public void testColumnsExpandWithGrid() {
+ public void testColumnsExpandWithGrid() throws IOException {
openTestURL();
GridElement grid = $(GridElement.class).first();
- int[] widths = new int[GridWidthIncrease.COLUMN_COUNT];
+ double accuracy = 1.0d;
+ DesiredCapabilities cap = getDesiredCapabilities();
+ if (BrowserUtil.isIE(cap, 8) || BrowserUtil.isIE(cap, 9)
+ || BrowserUtil.isPhantomJS(cap)) {
+ accuracy = 2.0d;
+ }
+
for (int i = 0; i < INCREASE_COUNT; ++i) {
- int totalWidth = 0;
$(ButtonElement.class).first().click();
+ int prevWidth = 0;
for (int c = 0; c < GridWidthIncrease.COLUMN_COUNT; ++c) {
int width = grid.getCell(0, c).getSize().getWidth();
- totalWidth += width;
- widths[c] = width;
if (c > 0) {
// check that columns are roughly the same width.
- assertEquals("Difference in column widths", widths[c],
- widths[c - 1], 1.0d);
+ assertEquals("Difference in column widths", prevWidth,
+ width, accuracy);
}
+ prevWidth = width;
}
- // Column widths should be the same as table wrapper size
- assertTrue(totalWidth == grid.getTableWrapper().getSize()
- .getWidth());
+ /*
+ * Column widths should be the same as table wrapper size. Since
+ * Selenium doesn't support subpixels correctly, we use a rough
+ * estimation.
+ */
+ assertEquals(grid.getRow(0).getSize().getWidth(), grid
+ .getTableWrapper().getSize().getWidth(), accuracy);
}
}
}
gridElement = getGridElement();
headerCells = gridElement.getHeaderCells(0);
final int size = headerCells.size();
- // skip last column since there is a bug in the width of the last column
- for (int i = 0; i < size - 1; i++) {
+ for (int i = 0; i < size; i++) {
+ // Avoid issues with inaccuracies regarding subpixels.
assertEquals(
"Column widths don't match after reset, index after flip "
- + i,
- columnWidths.get(i),
- Integer.valueOf(headerCells.get(size - 1 - i).getSize()
- .getWidth()));
+ + i, columnWidths.get(i),
+ headerCells.get(size - 1 - i).getSize().getWidth(), 1.0d);
}
}