}-*/;
+ /**
+ * Retrieves the given computed property as an integer
+ *
+ * Returns 0 if the property cannot be converted to an integer
+ *
+ * @param name
+ * the property to retrieve
+ * @return the integer value of the property or 0
+ */
public final int getIntProperty(String name) {
Profiler.enter("ComputedStyle.getIntProperty");
String value = getProperty(name);
return result;
}
+ /**
+ * Retrieves the given computed property as a double
+ *
+ * Returns NaN if the property cannot be converted to a double
+ *
+ * @param name
+ * the property to retrieve
+ * @return the double value of the property
+ */
+ public final int getDoubleProperty(String name) {
+ Profiler.enter("ComputedStyle.getDoubleProperty");
+ String value = getProperty(name);
+ int result = parseDoubleNative(value);
+ Profiler.leave("ComputedStyle.getDoubleProperty");
+ return result;
+ }
+
/**
* Get current margin values from the DOM. The array order is the default
* CSS order: top, right, bottom, left.
return border;
}
+ /**
+ * Returns the current width from the DOM.
+ *
+ * @since
+ * @return the computed width
+ */
+ public double getWidth() {
+ return getDoubleProperty("width");
+ }
+
+ /**
+ * Returns the current height from the DOM.
+ *
+ * @since
+ * @return the computed height
+ */
+ public double getHeight() {
+ return getDoubleProperty("height");
+ }
+
/**
* Takes a String value e.g. "12px" and parses that to Integer 12.
*
return number;
}-*/;
+ /**
+ * Takes a String value e.g. "12.3px" and parses that to a double, 12.3.
+ *
+ * @param String
+ * a value starting with a number
+ * @return the value from the string before any non-numeric characters or
+ * NaN if the value cannot be parsed as a number
+ */
+ private static native int parseDoubleNative(final String value)
+ /*-{
+ return parseFloat(value);
+ }-*/;
+
}
}
private static int detectedScrollbarSize = -1;
+ private static int detectedSubPixelRoundingFactor = -1;
public static int getNativeScrollbarSize() {
if (detectedScrollbarSize < 0) {
return heightWithBorder - heightWithoutBorder;
}
}-*/;
+
+ /**
+ * Rounds the given size up to a value which the browser will accept.
+ *
+ * Safari/WebKit uses 1/64th of a pixel to enable using integer math
+ * (http://trac.webkit.org/wiki/LayoutUnit).
+ *
+ * Firefox uses 1/60th of a pixel because it is divisible by three
+ * (https://bugzilla.mozilla.org/show_bug.cgi?id=1070940)
+ *
+ * @since
+ * @param size
+ * the value to round
+ * @return the rounded value
+ */
+ public static double roundSizeUp(double size) {
+ return roundSize(size, true);
+ }
+
+ /**
+ * Rounds the given size down to a value which the browser will accept.
+ *
+ * Safari/WebKit uses 1/64th of a pixel to enable using integer math
+ * (http://trac.webkit.org/wiki/LayoutUnit).
+ *
+ * Firefox uses 1/60th of a pixel because it is divisible by three
+ * (https://bugzilla.mozilla.org/show_bug.cgi?id=1070940)
+ *
+ * IE9+ uses 1/100th of a pixel
+ *
+ * @since
+ * @param size
+ * the value to round
+ * @return the rounded value
+ */
+ public static double roundSizeDown(double size) {
+ return roundSize(size, false);
+ }
+
+ private static double roundSize(double size, boolean roundUp) {
+ if (BrowserInfo.get().isIE8()) {
+ if (roundUp) {
+ return Math.ceil(size);
+ } else {
+ return (int) size;
+ }
+ }
+
+ double factor = getSubPixelRoundingFactor();
+ if (factor < 0 || size < 0) {
+ return size;
+ }
+
+ if (roundUp) {
+ return roundSizeUp(size, factor);
+ } else {
+ return roundSizeDown(size, factor);
+ }
+ }
+
+ /**
+ * Returns the factor used by browsers to round subpixel values
+ *
+ * @since
+ * @return the factor N used by the browser when storing subpixels as X+Y/N
+ */
+ private static double getSubPixelRoundingFactor() {
+ // Detects how the browser does subpixel rounding
+ // Currently Firefox uses 1/60th pixels
+ // and Safari uses 1/64th pixels
+ // IE 1/100th pixels
+ if (detectedSubPixelRoundingFactor != -1) {
+ return detectedSubPixelRoundingFactor;
+ }
+
+ double probeSize = 0.999999;
+ DivElement div = Document.get().createDivElement();
+ Document.get().getBody().appendChild(div);
+ div.getStyle().setHeight(probeSize, Unit.PX);
+ ComputedStyle computedStyle = new ComputedStyle(div);
+ double computedHeight = computedStyle.getHeight();
+
+ if (computedHeight < probeSize) {
+ // Rounded down by browser, all browsers but Firefox do this
+ // today
+ detectedSubPixelRoundingFactor = (int) Math
+ .round(1.0 / (1.0 - computedHeight));
+ } else {
+ // Rounded up / to nearest by browser
+ probeSize = 1;
+
+ while (computedStyle.getHeight() != 0.0) {
+ computedHeight = computedStyle.getHeight();
+ probeSize /= 2.0;
+ div.getStyle().setHeight(probeSize, Unit.PX);
+ }
+
+ detectedSubPixelRoundingFactor = (int) Math
+ .round(1.0 / computedHeight);
+ }
+
+ div.removeFromParent();
+ return detectedSubPixelRoundingFactor;
+ }
+
+ private static double roundSizeUp(double size, double divisor) {
+ // In: 12.51, 60.0
+
+ // 12
+ double integerPart = (int) size;
+
+ // (12.51 - 12) * 60 = 30.6
+ double nrFractions = (size - integerPart) * divisor;
+
+ // 12 + ceil(30.6) / 60 = 12 + 31/60 = 12.51666
+ return integerPart + (Math.ceil(nrFractions)) / divisor;
+ }
+
+ private static double roundSizeDown(double size, double divisor) {
+ // In: 12.51, 60.0
+
+ // 12
+ double integerPart = (int) size;
+
+ // (12.51 - 12) * 60 = 30.6
+ double nrFractions = (size - integerPart) * divisor;
+
+ // 12 + int(30.6) / 60 = 12 + 30/60 = 12.5
+ return integerPart + ((int) nrFractions) / divisor;
+ }
}
double scrollContentHeight = body.calculateTotalRowHeight()
+ body.spacerContainer.getSpacerHeightsSum();
double scrollContentWidth = columnConfiguration.calculateRowWidth();
-
double tableWrapperHeight = heightOfEscalator;
double tableWrapperWidth = widthOfEscalator;
.getCalculatedColumnsWidth(Range.between(
columnConfiguration.getFrozenColumnCount(),
columnConfiguration.getColumnCount()));
- unfrozenPixels -= subpixelBrowserBugDetector.getActiveAdjustment();
double frozenPixels = scrollContentWidth - unfrozenPixels;
double hScrollOffsetWidth = tableWrapperWidth - frozenPixels;
horizontalScrollbar.setOffsetSize(hScrollOffsetWidth);
rowElement.insertBefore(cellClone, cellOriginal);
double requiredWidth = WidgetUtil
.getRequiredWidthBoundingClientRectDouble(cellClone);
-
if (BrowserInfo.get().isIE()) {
/*
* IE browsers have some issues with subpixels. Occasionally
* @return the width of a row, in pixels
*/
public double calculateRowWidth() {
- return getCalculatedColumnsWidth(Range.between(0, getColumnCount()))
- - subpixelBrowserBugDetector.getActiveAdjustment();
+ return getCalculatedColumnsWidth(Range.between(0, getColumnCount()));
}
private void assertArgumentsAreValidAndWithinRange(final int index,
*/
@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
int index = entry.getKey().intValue();
double width = entry.getValue().doubleValue();
- if (index == getColumnCount() - 1) {
- subpixelBrowserBugDetector.invalidateFix();
- }
-
checkValidColumnIndex(index);
+
+ // Not all browsers will accept any fractional size..
+ width = WidgetUtil.roundSizeDown(width);
columns.get(index).setWidth(width);
}
body.reapplyColumnWidths();
footer.reapplyColumnWidths();
- subpixelBrowserBugDetector.checkAndFix();
-
recalculateElementSizes();
}
}
}
- private class SubpixelBrowserBugDetector {
- private static final double SUBPIXEL_ADJUSTMENT = .1;
- private boolean fixActive = 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 (!fixActive && hasSubpixelBrowserBug()) {
- fixSubpixelBrowserBug();
- fixActive = true;
- }
- }
-
- private double getActiveAdjustment() {
- if (fixActive) {
- return -SUBPIXEL_ADJUSTMENT;
- } else {
- return 0.0;
- }
- }
-
- public void invalidateFix() {
- adjustBookkeepingPixels(SUBPIXEL_ADJUSTMENT);
- fixActive = 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.
}
};
- private final SubpixelBrowserBugDetector subpixelBrowserBugDetector = new SubpixelBrowserBugDetector();
-
private final ElementPositionBookkeeper positions = new ElementPositionBookkeeper();
/**