diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ConstrainedColumnResize.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ConstrainedColumnResize.java new file mode 100644 index 00000000000..27a0434e0c5 --- /dev/null +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ConstrainedColumnResize.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javafx.scene.control; + +import java.util.List; +import javafx.css.PseudoClass; +import javafx.scene.Node; +import javafx.scene.control.ConstrainedColumnResizeBase; +import javafx.scene.control.ResizeFeaturesBase; +import javafx.scene.control.TableColumnBase; +import javafx.scene.control.TableView; +import javafx.scene.control.TreeTableView; +import javafx.util.Callback; + +/** + * A constrained column resize implementation that honors all Tree/TableColumn constraints - + * minimum, preferred, and maximum width. + */ +public class ConstrainedColumnResize extends ConstrainedColumnResizeBase { + + public enum ResizeMode { + AUTO_RESIZE_FLEX_HEAD, + AUTO_RESIZE_FLEX_TAIL, // will be used to replace a (deprecated) CONSTRAINED_RESIZE_POLICY. + AUTO_RESIZE_NEXT_COLUMN, + AUTO_RESIZE_SUBSEQUENT_COLUMNS, + AUTO_RESIZE_LAST_COLUMN, + AUTO_RESIZE_ALL_COLUMNS + } + + private final ResizeMode mode; + + public ConstrainedColumnResize(ResizeMode m) { + this.mode = m; + } + + public boolean constrainedResize(ResizeFeaturesBase rf, + List> visibleLeafColumns) { + + double contentWidth = rf.getContentWidth(); + if (contentWidth == 0.0) { + return false; + } + + ResizeHelper h = new ResizeHelper(rf, contentWidth, visibleLeafColumns, mode); + h.resizeToContentWidth(); + + boolean rv; + TableColumnBase column = rf.getColumn(); + if (column == null) { + rv = false; + } else { + rv = h.resizeColumn(column); + } + + // conditionally remove pseudoclass (given by this policy toString()) when the columns + // are narrower than the available area and the rightmost column boundary line needs to be drawn. + // hint: search modena.css for "constrained-resize" token + Node n = rf.getTableControl(); + PseudoClass pc = PseudoClass.getPseudoClass(toString()); + boolean wide = h.applySizes(); + boolean current = n.getPseudoClassStates().contains(pc); + if (wide != current) { + if (wide) { + n.pseudoClassStateChanged(pc, true); + } else { + n.pseudoClassStateChanged(pc, false); + } + } + return rv; + } + + public static TablePolicy forTable(ResizeMode m) { + return new TablePolicy(m); + } + + public static TreeTablePolicy forTreeTable(ResizeMode m) { + return new TreeTablePolicy(m); + } + + public static class TablePolicy + extends ConstrainedColumnResize + implements Callback { + + public TablePolicy(ResizeMode m) { + super(m); + } + + @Override + public Boolean call(TableView.ResizeFeatures rf) { + List> visibleLeafColumns = rf.getTable().getVisibleLeafColumns(); + return constrainedResize(rf, visibleLeafColumns); + } + } + + public static class TreeTablePolicy + extends ConstrainedColumnResize + implements Callback { + + public TreeTablePolicy(ResizeMode m) { + super(m); + } + + @Override + public Boolean call(TreeTableView.ResizeFeatures rf) { + List> visibleLeafColumns = rf.getTable().getVisibleLeafColumns(); + return constrainedResize(rf, visibleLeafColumns); + } + } +} diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ResizeHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ResizeHelper.java new file mode 100644 index 00000000000..1be646277cd --- /dev/null +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ResizeHelper.java @@ -0,0 +1,612 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.javafx.scene.control; + +import java.util.BitSet; +import java.util.List; +import javafx.scene.control.ResizeFeaturesBase; +import javafx.scene.control.TableColumnBase; +import javafx.scene.layout.Region; + +/** + * Helper class for Tree/TableView constrained column resize policies. + */ +public class ResizeHelper { + private static final int SMALL_DELTA = 32; + private static final double EPSILON = 0.000001; + private final ResizeFeaturesBase rf; + private final double target; + private final List> columns; + private final int count; + private final ConstrainedColumnResize.ResizeMode mode; + private final double[] size; + private final double[] min; + private final double[] pref; + private final double[] max; + private final BitSet skip; + private final Region snap; + + public ResizeHelper(ResizeFeaturesBase rf, + double target, + List> columns, + ConstrainedColumnResize.ResizeMode mode) { + this.rf = rf; + this.snap = (rf.getTableControl().isSnapToPixel() ? rf.getTableControl() : null); + this.columns = columns; + this.mode = mode; + this.target = snap(target); + this.count = columns.size(); + + size = new double[count]; + min = new double[count]; + pref = new double[count]; + max = new double[count]; + skip = new BitSet(count); + + for (int i = 0; i < count; i++) { + TableColumnBase c = columns.get(i); + size[i] = c.getWidth(); + + if (c.isResizable()) { + double cmin = c.getMinWidth(); + double cmax = c.getMaxWidth(); + min[i] = cmin; + max[i] = cmax; + pref[i] = clip(c.getPrefWidth(), cmin, cmax); + } else { + skip.set(i, true); + } + } + } + + public void resizeToContentWidth() { + boolean needsAnotherPass; + do { + needsAnotherPass = false; + double sumWidths = 0.0; + double sumMins = 0; + for (int i = 0; i < count; i++) { + sumWidths += size[i]; + sumMins += min[i]; + } + + if (sumMins >= target) { + return; + } + + double delta = target - snap(sumWidths); + if (isZero(delta)) { + return; + } + + // remove fixed and skipped columns from consideration + double total = 0; + for (int i = 0; i < count; i++) { + if (!skip.get(i)) { + total += pref[i]; + } + } + + if (isZero(total)) { + return; + } + + if (Math.abs(delta) < SMALL_DELTA) { + distributeSmallDelta(delta); + return; + } + + for (int i = 0; i < count; i++) { + if (skip.get(i)) { + continue; + } + + double w = size[i] + (delta * pref[i] / total); + if (w < min[i]) { + w = min[i]; + skip.set(i, true); + needsAnotherPass = true; + } else if (w > max[i]) { + w = max[i]; + skip.set(i, true); + needsAnotherPass = true; + } + + size[i] = w; + + if (needsAnotherPass) { + resetSizeChanges(); + break; + } + } + } while (needsAnotherPass); + } + + /** + * Applies computed column widths to the tree/table columns, + * snapping coordinates if required. + * + * @return true if sum of columns equals or greater than the target width + */ + public boolean applySizes() { + double pos = 0.0; + double prev = 0.0; + + for (int i = 0; i < count; i++) { + TableColumnBase c = columns.get(i); + if (c.isResizable()) { + pos = snap(pos + size[i]); + double w = (pos - prev); + rf.setColumnWidth(c, w); + } else { + pos = pos + size[i]; + } + prev = pos; + } + + return (pos > target); + } + + protected static double clip(double v, double min, double max) { + if (v < min) { + return min; + } else if (v > max) { + return max; + } + return v; + } + + public boolean resizeColumn(TableColumnBase column) { + double delta = rf.getDelta(); + // need to find the last leaf column of the given column - it is this + // column that we actually resize from. If this column is a leaf, then we use it. + TableColumnBase leafColumn = column; + while (leafColumn.getColumns().size() > 0) { + leafColumn = leafColumn.getColumns().get(leafColumn.getColumns().size() - 1); + } + + if (!leafColumn.isResizable()) { + return false; + } + + int ix = columns.indexOf(leafColumn); + boolean expanding = delta > 0.0; + double allowedDelta = getAllowedDelta(ix, expanding); + if (isZero(allowedDelta)) { + return false; + } + + int ct = markOppositeColumns(ix); + if (ct == 0) { + return false; + } + + double d = computeAllowedDelta(!expanding); + if (isZero(d)) { + return false; + } + + allowedDelta = Math.min(Math.abs(delta), Math.min(allowedDelta, d)); + if (!expanding) { + allowedDelta = -allowedDelta; + } + + if (isCornerCase(allowedDelta, ix)) { + return false; + } + + return distributeDelta(ix, allowedDelta); + } + + protected boolean isCornerCase(double delta, int ix) { + boolean isResizingLastColumn = (ix == count - 2); + if (isResizingLastColumn) { + if (delta > 0) { + int i = count - 1; + if (size[i] <= min[i]) { + // last column hit min constraint + return true; + } + } + } + + return false; + } + + /** non-negative */ + protected double getAllowedDelta(int ix, boolean expanding) { + if (expanding) { + return Math.abs(max[ix] - size[ix]); + } else { + return Math.abs(min[ix] - size[ix]); + } + } + + /** updates skip bitset with columns that might be resized, and returns the number of the opposite columns */ + protected int markOppositeColumns(int ix) { + switch (mode) { + case AUTO_RESIZE_NEXT_COLUMN: + setSkip(0, ix + 1); + setSkip(ix + 2, columns.size()); + break; + case AUTO_RESIZE_FLEX_HEAD: + case AUTO_RESIZE_FLEX_TAIL: + case AUTO_RESIZE_SUBSEQUENT_COLUMNS: + setSkip(0, ix + 1); + break; + case AUTO_RESIZE_LAST_COLUMN: + setSkip(0, Math.max(ix + 1, columns.size() - 1)); + break; + case AUTO_RESIZE_ALL_COLUMNS: + default: + setSkip(ix, ix + 1); + break; + } + + return count - skip.cardinality(); + } + + /** range set with limit check */ + protected void setSkip(int from, int toExclusive) { + if (from < 0) { + from = 0; + } else if (from >= count) { + return; + } + int to = Math.min(count, toExclusive); + if (from < to) { + skip.set(from, to); + } + } + + /** returns the allowable delta for all of the opposite columns */ + protected double computeAllowedDelta(boolean expanding) { + double delta = 0; + int i = 0; + for (;;) { + i = skip.nextClearBit(i); + // are we at the end? + if (i >= count) { + break; + } + + if (expanding) { + delta += (max[i] - size[i]); + } else { + delta += (size[i] - min[i]); + } + + i++; + } + return delta; + } + + protected boolean distributeDelta(int ix, double delta) { + int ct = count - skip.cardinality(); + switch (ct) { + case 0: + return false; + case 1: + int oppx = skip.nextClearBit(0); + size[ix] += delta; + size[oppx] -= delta; + return true; + default: + size[ix] += delta; + double adj; + + switch (mode) { + case AUTO_RESIZE_FLEX_HEAD: + adj = distributeDeltaFlexHead(-delta); + break; + case AUTO_RESIZE_FLEX_TAIL: + adj = distributeDeltaFlexTail(-delta); + break; + default: + if (Math.abs(delta) < SMALL_DELTA) { + distributeSmallDelta(-delta); + } else { + distributeDeltaRemainingColumns(-delta); + } + adj = 0.0; + break; + } + + size[ix] += adj; + + return true; + } + } + + protected double distributeDeltaFlexHead(double delta) { + if (delta < 0) { + // when shrinking, first resize columns that are wider than their preferred width + for (int i = 0; i < count; i++) { + if (skip.get(i)) { + continue; + } + + if (size[i] > pref[i]) { + delta = resize(i, delta); + if (isZero(delta)) { + break; + } + } + } + } else { + // when expanding, first resize columns that are narrower than their preferred width + for (int i = 0; i < count; i++) { + if (skip.get(i)) { + continue; + } + + if (size[i] < pref[i]) { + delta = resize(i, delta); + if (isZero(delta)) { + break; + } + } + } + } + + for (int i = 0; i < count; i++) { + if (skip.get(i)) { + continue; + } + + delta = resize(i, delta); + if (isZero(delta)) { + break; + } + } + return delta; + } + + protected double distributeDeltaFlexTail(double delta) { + if (delta < 0) { + // when shrinking, first resize columns that are wider than their preferred width + for (int i = count - 1; i >= 0; --i) { + if (skip.get(i)) { + continue; + } + + if (size[i] > pref[i]) { + delta = resize(i, delta); + if (isZero(delta)) { + break; + } + } + } + } else { + // when expanding, first resize columns that are narrower than their preferred width + for (int i = count - 1; i >= 0; --i) { + if (skip.get(i)) { + continue; + } + + if (size[i] < pref[i]) { + delta = resize(i, delta); + if (isZero(delta)) { + break; + } + } + } + } + + for (int i = count - 1; i >= 0; --i) { + if (skip.get(i)) { + continue; + } + + delta = resize(i, delta); + if (isZero(delta)) { + break; + } + } + return delta; + } + + protected double resize(int ix, double delta) { + double w = size[ix] + delta; + if (w < min[ix]) { + delta = (w - min[ix]); + w = min[ix]; + } else if (w > max[ix]) { + delta = (w - max[ix]); + w = max[ix]; + } else { + delta = 0.0; + } + + size[ix] = w; + return delta; + } + + protected void distributeDeltaRemainingColumns(double delta) { + boolean needsAnotherPass; + + do { + double total = 0; + for (int i = 0; i < count; i++) { + if (!skip.get(i)) { + total += pref[i]; + } + } + + if (isZero(total)) { + return; + } + + needsAnotherPass = false; + + for (int i = 0; i < count; i++) { + if (skip.get(i)) { + continue; + } + + double w = size[i] + (delta * pref[i] / total); + if (w < min[i]) { + w = min[i]; + skip.set(i, true); + needsAnotherPass = true; + delta -= (w - size[i]); + } else if (w > max[i]) { + w = max[i]; + skip.set(i, true); + needsAnotherPass = true; + delta -= (w - size[i]); + } + + size[i] = w; + + if (needsAnotherPass) { + resetSizeChanges(); + break; + } + } + } while (needsAnotherPass); + } + + /** + * for small deltas, we use a simpler, but more expensive algorithm to distribute space in small steps, + * each time favoring a column that is further away from its preferred width. + */ + protected void distributeSmallDelta(double delta) { + if (delta < 0) { + while (delta < 0.0) { + double d = Math.max(-1.0, delta); + double rem = shrinkSmall(d); + if (Double.isNaN(rem)) { + return; + } + + delta -= (d - rem); + } + } else { + while (delta > 0.0) { + double d = Math.min(1.0, delta); + double rem = expandSmall(d); + if (Double.isNaN(rem)) { + return; + } + + delta -= (d - rem); + } + } + } + + /** + * Finds the best column to shrink, then reduces its width. + * Adds the column to skip list if the column width hits a constraint after adjustement. + * @return unused portion of delta, or Double.NaN if it did not find a good candidate + */ + protected double shrinkSmall(double delta) { + double dist = Double.NEGATIVE_INFINITY; + int ix = -1; + for (int i = 0; i < count; i++) { + if (!skip.get(i)) { + double d = size[i] - pref[i]; + if (d > dist) { + dist = d; + ix = i; + } + } + } + + if (ix < 0) { + return Double.NaN; + } + + double rem = 0.0; + double w = size[ix] + delta; + if (w < min[ix]) { + rem = (w - min[ix]); + w = min[ix]; + skip.set(ix); + } + size[ix] = w; + return rem; + } + + /** + * Finds the best column to shrink, then reduces its width. + * Adds the column to skip list if the column width hits a constraint after adjustement. + * @return unused portion of delta, or Double.NaN if it did not find a good candidate + */ + protected double expandSmall(double delta) { + double dist = Double.NEGATIVE_INFINITY; + int ix = -1; + for (int i = 0; i < count; i++) { + if (!skip.get(i)) { + double d = pref[i] - size[i]; + if (d > dist) { + dist = d; + ix = i; + } + } + } + + if (ix < 0) { + return Double.NaN; + } + + double rem = 0.0; + double w = size[ix] + delta; + if (w > max[ix]) { + rem = (w - max[ix]); + w = max[ix]; + skip.set(ix); + } + size[ix] = w; + return rem; + } + + protected void resetSizeChanges() { + for (int i = 0; i < count; i++) { + if (!skip.get(i)) { + size[i] = columns.get(i).getWidth(); + } + } + } + + protected double sumSizes() { + double sum = 0; + for (int i = 0; i < count; i++) { + sum += size[i]; + } + return sum; + } + + protected static boolean isZero(double x) { + return Math.abs(x) < EPSILON; + } + + protected double snap(double x) { + if (snap == null) { + return x; + } + return snap.snapSpaceX(x); + } +} diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/ConstrainedColumnResizeBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/ConstrainedColumnResizeBase.java new file mode 100644 index 00000000000..1a8f2a43f07 --- /dev/null +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/ConstrainedColumnResizeBase.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.control; + +/** + * Base class for a constrained column resize policy. + * Setting any policy that extends this class on a Tree/TableView results in + * disabling of its horizontal scroll bar. + * + * @see TableView#columnResizePolicyProperty + * @see TreeTableView#columnResizePolicyProperty + * + * @since 20 + */ +public abstract class ConstrainedColumnResizeBase { + /** + * Constructor for subclasses to call. + */ + public ConstrainedColumnResizeBase() { + } + + @Override + public String toString() { + // name of a pseudo-style set on a Tree/TableView when a constrained resize policy is in effect + return "constrained-resize"; + } +} diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/ResizeFeaturesBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/ResizeFeaturesBase.java index 6c76240574c..f82842a81fb 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/ResizeFeaturesBase.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/ResizeFeaturesBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,11 +29,11 @@ /** - * An immutable wrapper class for use by the column resize policies offered by + * A wrapper class for use by the column resize policies offered by * controls such as {@link TableView} and {@link TreeTableView}. * @since JavaFX 8.0 */ -public class ResizeFeaturesBase { +public abstract class ResizeFeaturesBase { private final TableColumnBase column; private final Double delta; @@ -52,6 +52,24 @@ public ResizeFeaturesBase(@NamedArg("column") TableColumnBase column, @Name this.delta = delta; } + /** + * Returns the width of the area available for columns. + * + * @return the width availabe for columns + * + * @since 20 + */ + public abstract double getContentWidth(); + + /** + * Returns the associated TreeView or TreeTableView control. + * + * @return the control in which the resize is occurring + * + * @since 20 + */ + public abstract Control getTableControl(); + /** * Returns the column upon which the resize is occurring, or null * if this ResizeFeatures instance was created as a result of a @@ -67,4 +85,16 @@ public ResizeFeaturesBase(@NamedArg("column") TableColumnBase column, @Name * resize operation */ public Double getDelta() { return delta; } + + /** + * Sets the column width during the resizing pass. + * + * @param col column being changed + * @param width desired column width + * + * @since 20 + */ + public void setColumnWidth(TableColumnBase col, double width) { + col.doSetWidth(width); + } } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/TableUtil.java b/modules/javafx.controls/src/main/java/javafx/scene/control/TableUtil.java index 7ea4e801fe2..b7af7c00890 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/TableUtil.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/TableUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -182,189 +182,6 @@ static enum SortEventType { COLUMN_COMPARATOR_CHANGE } - - - - - /** - * The constrained resize algorithm used by TableView and TreeTableView. - * @param prop - * @param isFirstRun - * @param tableWidth - * @param visibleLeafColumns - * @return - */ - static boolean constrainedResize(ResizeFeaturesBase prop, - boolean isFirstRun, - double tableWidth, - List> visibleLeafColumns) { - TableColumnBase column = prop.getColumn(); - double delta = prop.getDelta(); - - /* - * There are two phases to the constrained resize policy: - * 1) Ensuring internal consistency (i.e. table width == sum of all visible - * columns width). This is often called when the table is resized. - * 2) Resizing the given column by __up to__ the given delta. - * - * It is possible that phase 1 occur and there be no need for phase 2 to - * occur. - */ - - boolean isShrinking; - double target; - double totalLowerBound = 0; - double totalUpperBound = 0; - - if (tableWidth == 0) return false; - - /* - * PHASE 1: Check to ensure we have internal consistency. Based on the - * Swing JTable implementation. - */ - // determine the width of all visible columns, and their preferred width - double colWidth = 0; - for (TableColumnBase col : visibleLeafColumns) { - colWidth += col.getWidth(); - } - - if (Math.abs(colWidth - tableWidth) > EPSILON) { - isShrinking = colWidth > tableWidth; - target = tableWidth; - - if (isFirstRun) { - // if we are here we have an inconsistency - these two values should be - // equal when this resizing policy is being used. - for (TableColumnBase col : visibleLeafColumns) { - totalLowerBound += col.getMinWidth(); - totalUpperBound += col.getMaxWidth(); - } - - // We run into trouble if the numbers are set to infinity later on - totalUpperBound = totalUpperBound == Double.POSITIVE_INFINITY ? - Double.MAX_VALUE : - (totalUpperBound == Double.NEGATIVE_INFINITY ? Double.MIN_VALUE : totalUpperBound); - - for (TableColumnBase col : visibleLeafColumns) { - double lowerBound = col.getMinWidth(); - double upperBound = col.getMaxWidth(); - - // Check for zero. This happens when the distribution of the delta - // finishes early due to a series of "fixed" entries at the end. - // In this case, lowerBound == upperBound, for all subsequent terms. - double newSize; - if (Math.abs(totalLowerBound - totalUpperBound) < EPSILON) { - newSize = lowerBound; - } else { - double f = (target - totalLowerBound) / (totalUpperBound - totalLowerBound); - newSize = Math.round(lowerBound + f * (upperBound - lowerBound)); - } - - double remainder = resize(col, newSize - col.getWidth()); - - target -= newSize + remainder; - totalLowerBound -= lowerBound; - totalUpperBound -= upperBound; - } - - isFirstRun = false; - } else { - double actualDelta = tableWidth - colWidth; - List> cols = visibleLeafColumns; - resizeColumns(cols, actualDelta); - } - } - - // At this point we can be happy in the knowledge that we have internal - // consistency, i.e. table width == sum of the width of all visible - // leaf columns. - - /* - * Column may be null if we just changed the resize policy, and we - * just wanted to enforce internal consistency, as mentioned above. - */ - if (column == null) { - return false; - } - - /* - * PHASE 2: Handling actual column resizing (by the user). Based on my own - * implementation (based on the UX spec). - */ - - isShrinking = delta < 0; - - // need to find the last leaf column of the given column - it is this - // column that we actually resize from. If this column is a leaf, then we - // use it. - TableColumnBase leafColumn = column; - while (leafColumn.getColumns().size() > 0) { - leafColumn = leafColumn.getColumns().get(leafColumn.getColumns().size() - 1); - } - - int colPos = visibleLeafColumns.indexOf(leafColumn); - int endColPos = visibleLeafColumns.size() - 1; - - // we now can split the observableArrayList into two subobservableArrayLists, representing all - // columns that should grow, and all columns that should shrink - // var growingCols = if (isShrinking) - // then table.visibleLeafColumns[colPos+1..endColPos] - // else table.visibleLeafColumns[0..colPos]; - // var shrinkingCols = if (isShrinking) - // then table.visibleLeafColumns[0..colPos] - // else table.visibleLeafColumns[colPos+1..endColPos]; - - - double remainingDelta = delta; - while (endColPos > colPos && remainingDelta != 0) { - TableColumnBase resizingCol = visibleLeafColumns.get(endColPos); - endColPos--; - - // if the column width is fixed, break out and try the next column - if (! resizingCol.isResizable()) continue; - - // for convenience we discern between the shrinking and growing columns - TableColumnBase shrinkingCol = isShrinking ? leafColumn : resizingCol; - TableColumnBase growingCol = !isShrinking ? leafColumn : resizingCol; - - // (shrinkingCol.width == shrinkingCol.minWidth) or (growingCol.width == growingCol.maxWidth) - - if (growingCol.getWidth() > growingCol.getPrefWidth()) { - // growingCol is willing to be generous in this case - it goes - // off to find a potentially better candidate to grow - List seq = visibleLeafColumns.subList(colPos + 1, endColPos + 1); - for (int i = seq.size() - 1; i >= 0; i--) { - TableColumnBase c = seq.get(i); - if (c.getWidth() < c.getPrefWidth()) { - growingCol = c; - break; - } - } - } - // - // if (shrinkingCol.width < shrinkingCol.prefWidth) { - // for (c in reverse table.visibleLeafColumns[colPos+1..endColPos]) { - // if (c.width > c.prefWidth) { - // shrinkingCol = c; - // break; - // } - // } - // } - - - - double sdiff = Math.min(Math.abs(remainingDelta), shrinkingCol.getWidth() - shrinkingCol.getMinWidth()); - -// System.out.println("\tshrinking " + shrinkingCol.getText() + " and growing " + growingCol.getText()); -// System.out.println("\t\tMath.min(Math.abs("+remainingDelta+"), "+shrinkingCol.getWidth()+" - "+shrinkingCol.getMinWidth()+") = " + sdiff); - - double delta1 = resize(shrinkingCol, -sdiff); - double delta2 = resize(growingCol, sdiff); - remainingDelta += isShrinking ? sdiff : -sdiff; - } - return remainingDelta == 0; - } - // function used to actually perform the resizing of the given column, // whilst ensuring it stays within the min and max bounds set on the column. // Returns the remaining delta if it could not all be applied. @@ -465,5 +282,4 @@ private static double resizeColumns(List> columns // see isClean above for why this is done return isClean ? 0.0 : remainingDelta; } - } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/TableView.java b/modules/javafx.controls/src/main/java/javafx/scene/control/TableView.java index dade52cfd7c..146e74fbccd 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/TableView.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/TableView.java @@ -36,16 +36,21 @@ import java.util.Set; import java.util.WeakHashMap; import java.util.function.IntPredicate; - +import com.sun.javafx.collections.MappingChange; +import com.sun.javafx.collections.NonIterableChange; import com.sun.javafx.logging.PlatformLogger.Level; +import com.sun.javafx.scene.control.ConstrainedColumnResize; import com.sun.javafx.scene.control.Logging; import com.sun.javafx.scene.control.Properties; +import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; import com.sun.javafx.scene.control.SelectedCellsMap; +import com.sun.javafx.scene.control.TableColumnComparatorBase.TableColumnComparator; import com.sun.javafx.scene.control.behavior.TableCellBehavior; import com.sun.javafx.scene.control.behavior.TableCellBehaviorBase; - -import javafx.beans.*; +import javafx.beans.DefaultProperty; +import javafx.beans.InvalidationListener; import javafx.beans.Observable; +import javafx.beans.WeakInvalidationListener; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; @@ -66,21 +71,16 @@ import javafx.css.Styleable; import javafx.css.StyleableDoubleProperty; import javafx.css.StyleableProperty; +import javafx.css.converter.SizeConverter; import javafx.event.EventHandler; import javafx.event.EventType; import javafx.scene.AccessibleAttribute; import javafx.scene.AccessibleRole; import javafx.scene.Node; +import javafx.scene.control.skin.TableViewSkin; import javafx.scene.layout.Region; import javafx.util.Callback; -import com.sun.javafx.collections.MappingChange; -import com.sun.javafx.collections.NonIterableChange; -import javafx.css.converter.SizeConverter; -import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; -import com.sun.javafx.scene.control.TableColumnComparatorBase.TableColumnComparator; -import javafx.scene.control.skin.TableViewSkin; - /** * The TableView control is designed to visualize an unlimited number of rows * of data, broken out into columns. A TableView is therefore very similar to the @@ -395,6 +395,86 @@ public class TableView extends Control { } }; + /** + * A resize policy that adjusts other columns in order to fit the table width. + * During UI adjustment, proportionately resizes all columns to preserve the total width. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS = + ConstrainedColumnResize.forTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_ALL_COLUMNS); + + /** + * A resize policy that adjusts the last column in order to fit the table width. + * During UI adjustment, resizes the last column only to preserve the total width. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_LAST_COLUMN = + ConstrainedColumnResize.forTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_LAST_COLUMN); + + /** + * A resize policy that adjusts the next column in order to fit the table width. + * During UI adjustment, resizes the next column the opposite way. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_NEXT_COLUMN = + ConstrainedColumnResize.forTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_NEXT_COLUMN); + + /** + * A resize policy that adjusts subsequent columns in order to fit the table width. + * During UI adjustment, proportionally resizes subsequent columns to preserve the total width. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_SUBSEQUENT_COLUMNS = + ConstrainedColumnResize.forTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_SUBSEQUENT_COLUMNS); + + /** + * A resize policy that adjusts columns, starting with the next one, in order to fit the table width. + * During UI adjustment, resizes the next column to preserve the total width. When the next column + * cannot be further resized due to a constraint, the following column gets resized, and so on. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_FLEX_NEXT_COLUMN = + ConstrainedColumnResize.forTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_FLEX_HEAD); + + /** + * A resize policy that adjusts columns, starting with the last one, in order to fit the table width. + * During UI adjustment, resizes the last column to preserve the total width. When the last column + * cannot be further resized due to a constraint, the column preceding the last one gets resized, and so on. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN = + ConstrainedColumnResize.forTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_FLEX_TAIL); + /** *

Simple policy that ensures the width of all visible leaf columns in * this table sum up to equal the width of the table itself. @@ -406,26 +486,12 @@ public class TableView extends Control { * rightmost column until it reaches minimum width and so on. When all right * hand side columns reach minimum size, the user cannot increase the size of * resized column any more. + * + * @deprecated Use {@link #CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN} instead. */ - public static final Callback CONSTRAINED_RESIZE_POLICY = new Callback<>() { - - private boolean isFirstRun = true; - - @Override public String toString() { - return "constrained-resize"; - } - - @Override public Boolean call(ResizeFeatures prop) { - TableView table = prop.getTable(); - List> visibleLeafColumns = table.getVisibleLeafColumns(); - Boolean result = TableUtil.constrainedResize(prop, - isFirstRun, - table.contentWidth, - visibleLeafColumns); - isFirstRun = ! isFirstRun ? false : ! result; - return result; - } - }; + @Deprecated(since="20") + public static final Callback CONSTRAINED_RESIZE_POLICY = + CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN; /** * The default {@link #sortPolicyProperty() sort policy} that this TableView @@ -437,7 +503,8 @@ public class TableView extends Control { * @since JavaFX 8.0 */ public static final Callback DEFAULT_SORT_POLICY = new Callback<>() { - @Override public Boolean call(TableView table) { + @Override + public Boolean call(TableView table) { try { ObservableList itemsList = table.getItems(); if (itemsList instanceof SortedList) { @@ -1871,6 +1938,16 @@ public ResizeFeatures(TableView table, TableColumn column, Double delta) public TableView getTable() { return table; } + + @Override + public Control getTableControl() { + return table; + } + + @Override + public double getContentWidth() { + return table.contentWidth; + } } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java b/modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java index 4bd891a02e9..1000123a5c5 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java @@ -39,7 +39,16 @@ import java.util.Set; import java.util.WeakHashMap; import java.util.function.IntPredicate; - +import com.sun.javafx.collections.MappingChange; +import com.sun.javafx.collections.NonIterableChange; +import com.sun.javafx.scene.control.ConstrainedColumnResize; +import com.sun.javafx.scene.control.Properties; +import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; +import com.sun.javafx.scene.control.SelectedCellsMap; +import com.sun.javafx.scene.control.TableColumnComparatorBase; +import com.sun.javafx.scene.control.behavior.TableCellBehavior; +import com.sun.javafx.scene.control.behavior.TableCellBehaviorBase; +import com.sun.javafx.scene.control.behavior.TreeTableCellBehavior; import javafx.application.Platform; import javafx.beans.DefaultProperty; import javafx.beans.InvalidationListener; @@ -531,6 +540,86 @@ public static int getNodeLevel(TreeItem node) { } }; + /** + * A resize policy that adjusts other columns in order to fit the tree table width. + * During UI adjustment, proportionately resizes all columns to preserve the total width. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS = + ConstrainedColumnResize.forTreeTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_ALL_COLUMNS); + + /** + * A resize policy that adjusts the last column in order to fit the tree table width. + * During UI adjustment, resizes the last column only to preserve the total width. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_LAST_COLUMN = + ConstrainedColumnResize.forTreeTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_LAST_COLUMN); + + /** + * A resize policy that adjusts the next column in order to fit the tree table width. + * During UI adjustment, resizes the next column the opposite way. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_NEXT_COLUMN = + ConstrainedColumnResize.forTreeTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_NEXT_COLUMN); + + /** + * A resize policy that adjusts subsequent columns in order to fit the tree table width. + * During UI adjustment, proportionally resizes subsequent columns to preserve the total width. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_SUBSEQUENT_COLUMNS = + ConstrainedColumnResize.forTreeTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_SUBSEQUENT_COLUMNS); + + /** + * A resize policy that adjusts columns, starting with the next one, in order to fit the tree table width. + * During UI adjustment, resizes the next column to preserve the total width. When the next column + * cannot be further resized due to a constraint, the following column gets resized, and so on. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_FLEX_NEXT_COLUMN = + ConstrainedColumnResize.forTreeTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_FLEX_HEAD); + + /** + * A resize policy that adjusts columns, starting with the last one, in order to fit the table width. + * During UI adjustment, resizes the last column to preserve the total width. When the last column + * cannot be further resized due to a constraint, the column preceding the last one gets resized, and so on. + *

+ * When column constraints make it impossible to fit all the columns into the allowed area, + * the columns are either clipped, or an empty space appears. This policy disables the horizontal + * scroll bar. + * + * @since 20 + */ + public static final Callback CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN = + ConstrainedColumnResize.forTreeTable(ConstrainedColumnResize.ResizeMode.AUTO_RESIZE_FLEX_TAIL); + /** *

Simple policy that ensures the width of all visible leaf columns in * this table sum up to equal the width of the table itself. @@ -542,27 +631,12 @@ public static int getNodeLevel(TreeItem node) { * rightmost column until it reaches minimum width and so on. When all right * hand side columns reach minimum size, the user cannot increase the size of * resized column any more. + * + * @deprecated Use {@link #CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN} instead. */ + @Deprecated(since="20") public static final Callback CONSTRAINED_RESIZE_POLICY = - new Callback<>() { - - private boolean isFirstRun = true; - - @Override public String toString() { - return "constrained-resize"; - } - - @Override public Boolean call(TreeTableView.ResizeFeatures prop) { - TreeTableView table = prop.getTable(); - List> visibleLeafColumns = table.getVisibleLeafColumns(); - Boolean result = TableUtil.constrainedResize(prop, - isFirstRun, - table.contentWidth, - visibleLeafColumns); - isFirstRun = ! isFirstRun ? false : ! result; - return result; - } - }; + CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN; /** * The default {@link #sortPolicyProperty() sort policy} that this TreeTableView @@ -2203,6 +2277,16 @@ public ResizeFeatures(TreeTableView treeTable, TreeTableColumn column, D * @return the TreeTableView upon which the resize operation is occurring */ public TreeTableView getTable() { return treeTable; } + + @Override + public Control getTableControl() { + return treeTable; + } + + @Override + public double getContentWidth() { + return treeTable.contentWidth; + } } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableSkinUtils.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableSkinUtils.java index 9bd95b5d21e..44f8a8b8411 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableSkinUtils.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableSkinUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Node; +import javafx.scene.control.ConstrainedColumnResizeBase; import javafx.scene.control.Control; import javafx.scene.control.IndexedCell; import javafx.scene.control.ResizeFeaturesBase; @@ -201,7 +202,6 @@ public static ObjectProperty> itemsProperty(TableViewSkinB /** returns true if the column resize policy is constrained */ public static boolean isConstrainedResizePolicy(Callback x) { - return (x == TableView.CONSTRAINED_RESIZE_POLICY) || - (x == TreeTableView.CONSTRAINED_RESIZE_POLICY); + return (x instanceof ConstrainedColumnResizeBase); } } diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/ResizeHelperTestBase.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/ResizeHelperTestBase.java new file mode 100644 index 00000000000..758df192ea0 --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/ResizeHelperTestBase.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package test.javafx.scene.control; + +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; +import javafx.scene.control.TableColumnBase; +import org.junit.After; +import test.com.sun.javafx.scene.control.infrastructure.StageLoader; + +/** + * Base class for tests the new column resize policies. + * The two descendants are TableViewResizeTest and TreeTableViewResizeTest. + */ +public abstract class ResizeHelperTestBase { + + public enum Cmd { + ROWS, + COL, + MIN, + PREF, + MAX, + COMBINE + } + + protected static final double EPSILON = 0.000001; + protected StageLoader stageLoader; + + @After + public void after() { + if (stageLoader != null) { + stageLoader.dispose(); + } + } + + protected void checkInvariants(List> cols) { + for (TableColumnBase c: cols) { + assertTrue("violated min constraint: w=" + c.getWidth() + " min=" + c.getMinWidth(), + c.getWidth() >= c.getMinWidth()); + assertTrue("violated max constraint: w=" + c.getWidth() + " max=" + c.getMaxWidth(), + c.getWidth() <= c.getMaxWidth()); + } + } + + protected static class SpecGen { + public static final int[] WIDTHS = { + 0, 10, 100, 10_000, 200, 50 + }; + private static final int LAST = 8; // 2^3 min,pref,max + 1 fixed + private final int[] phase; + + public SpecGen(int numcols) { + this.phase = new int[numcols]; + } + + public boolean hasNext() { + int terminal = LAST; + for (int n: phase) { + if (n != terminal) { + return true; + } + } + return false; + } + + public Object[] next() { + ArrayList rv = new ArrayList<>(phase.length); + for (int i = 0; i < phase.length; i++) { + rv.add(Cmd.COL); + + int n = phase[i]; + if (n < 8) { + if ((n & 0x01) != 0) { + rv.add(Cmd.MIN); + rv.add(100); + } + + if ((n & 0x02) != 0) { + rv.add(Cmd.PREF); + rv.add(200 + 50 * i); + } + + if ((n & 0x04) != 0) { + rv.add(Cmd.MAX); + rv.add(200 + 100 * i); + } + } else if (n == LAST) { + rv.add(Cmd.MIN); + rv.add(50); + rv.add(Cmd.MAX); + rv.add(50); + } + } + return rv.toArray(); + } + } +} diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TableViewResizeTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TableViewResizeTest.java new file mode 100644 index 00000000000..0fe8e83deb4 --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TableViewResizeTest.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package test.javafx.scene.control; + +import static org.junit.Assert.assertEquals; +import java.util.List; +import javafx.beans.property.SimpleStringProperty; +import javafx.scene.control.ConstrainedColumnResizeBase; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumnBase; +import javafx.scene.control.TableView; +import javafx.scene.layout.BorderPane; +import javafx.util.Callback; +import org.junit.Test; +import com.sun.javafx.tk.Toolkit; +import test.com.sun.javafx.scene.control.infrastructure.StageLoader; + +/** + * Tests TableView constrained resize policies. + */ +public class TableViewResizeTest extends ResizeHelperTestBase { + + protected void checkInvariants(TableView t) { + List> cols = t.getColumns(); + checkInvariants(cols); + } + + protected static TableView createTable(Object[] spec) { + TableView table = new TableView(); + table.getSelectionModel().setCellSelectionEnabled(true); + table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + TableColumn lastColumn = null; + int id = 1; + + for (int i = 0; i < spec.length;) { + Object x = spec[i++]; + if (x instanceof Cmd cmd) { + switch (cmd) { + case COL: + TableColumn c = new TableColumn<>(); + table.getColumns().add(c); + c.setText("C" + table.getColumns().size()); + c.setCellValueFactory((f) -> new SimpleStringProperty(" ")); + lastColumn = c; + break; + case MAX: + { + int w = (int)(spec[i++]); + lastColumn.setMaxWidth(w); + } + break; + case MIN: + { + int w = (int)(spec[i++]); + lastColumn.setMinWidth(w); + } + break; + case PREF: + { + int w = (int)(spec[i++]); + lastColumn.setPrefWidth(w); + } + break; + case ROWS: + int n = (int)(spec[i++]); + for (int j = 0; j < n; j++) { + table.getItems().add(String.valueOf(n)); + } + break; + case COMBINE: + int ix = (int)(spec[i++]); + int ct = (int)(spec[i++]); + combineColumns(table, ix, ct, id++); + break; + default: + throw new Error("?" + cmd); + } + } else { + throw new Error("?" + x); + } + } + + return table; + } + + protected static void combineColumns(TableView t, int ix, int count, int name) { + TableColumn tc = new TableColumn<>(); + tc.setText("N" + name); + + for (int i = 0; i < count; i++) { + TableColumn c = (TableColumn)t.getColumns().remove(ix); + tc.getColumns().add(c); + } + t.getColumns().add(ix, tc); + } + + /** verify that a custom constrained resize policy can indeed be implemented using public APIs */ + @Test + public void testCanImplementCustomResizePolicy() { + double WIDTH = 15.0; + + // constrained resize policy that simply sets all column widths to WIDTH + class UserPolicy + extends ConstrainedColumnResizeBase + implements Callback { + + @Override + public Boolean call(TableView.ResizeFeatures rf) { + List> columns = rf.getTable().getVisibleLeafColumns(); + int sz = columns.size(); + // new public method getContentWidth() is visible + double w = rf.getContentWidth(); + for (TableColumnBase c: columns) { + // using added public method setColumnWidth() + rf.setColumnWidth(c, WIDTH); + } + return false; + } + } + + Object[] spec = { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL + }; + TableView table = createTable(spec); + + UserPolicy policy = new UserPolicy(); + table.setColumnResizePolicy(policy); + table.setPrefWidth(10); + + // verify the policy is in effect + stageLoader = new StageLoader(new BorderPane(table)); + Toolkit.getToolkit().firePulse(); + + for (TableColumn c: table.getColumns()) { + assertEquals(WIDTH, c.getWidth(), EPSILON); + } + + // resize and check again + table.setPrefWidth(10_000); + Toolkit.getToolkit().firePulse(); + + for (TableColumn c: table.getColumns()) { + assertEquals(WIDTH, c.getWidth(), EPSILON); + } + } + + /** + * Exhausive behavioral test. + * + * Goes through all the policies, all valid combinations of constraints, + * and widths increasing to MAX_WIDTH and back, + * checkint that the initial resize does not violate (min,max) constraints. + */ + //@Test // this test takes too much time! + public void testWidthChange() { + int[] COLUMNS = { + 0, 1, 2, 5 + }; + long start = System.currentTimeMillis(); + for (int numCols: COLUMNS) { + SpecGen gen = new SpecGen(numCols); + while (gen.hasNext()) { + Object[] spec = gen.next(); + TableView table = createTable(spec); + stageLoader = new StageLoader(new BorderPane(table)); + try { + for (int ip = 0; ip < POLICIES.length; ip++) { + Callback policy = createPolicy(ip); + table.setColumnResizePolicy(policy); + for (int width: gen.WIDTHS) { + table.setPrefWidth(width); + Toolkit.getToolkit().firePulse(); + checkInvariants(table); + } + } + } finally { + stageLoader.dispose(); + } + } + } + + System.out.println("elapsed time = " + (System.currentTimeMillis() - start) / 60_000 + " minutes."); + } + + protected static final Object[] POLICIES = { + TableView.CONSTRAINED_RESIZE_POLICY_FLEX_NEXT_COLUMN, + TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN, + TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS, + TableView.CONSTRAINED_RESIZE_POLICY_LAST_COLUMN, + TableView.CONSTRAINED_RESIZE_POLICY_NEXT_COLUMN, + TableView.CONSTRAINED_RESIZE_POLICY_SUBSEQUENT_COLUMNS + }; + + protected static Callback createPolicy(int ix) { + return (Callback)POLICIES[ix]; + } +} diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeTableViewResizeTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeTableViewResizeTest.java new file mode 100644 index 00000000000..48cfb66245d --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeTableViewResizeTest.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package test.javafx.scene.control; + +import static org.junit.Assert.assertEquals; +import java.util.List; +import javafx.beans.property.SimpleStringProperty; +import javafx.scene.control.ConstrainedColumnResizeBase; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TableColumnBase; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeTableColumn; +import javafx.scene.control.TreeTableView; +import javafx.scene.layout.BorderPane; +import javafx.util.Callback; +import org.junit.Test; +import com.sun.javafx.tk.Toolkit; +import test.com.sun.javafx.scene.control.infrastructure.StageLoader; + +/** + * Tests TreeTableView constrained resize policies. + */ +public class TreeTableViewResizeTest extends ResizeHelperTestBase { + + protected void checkInvariants(TreeTableView t) { + List> cols = t.getColumns(); + checkInvariants(cols); + } + + protected static TreeTableView createTable(Object[] spec) { + TreeTableView table = new TreeTableView(new TreeItem<>(null)); + table.getSelectionModel().setCellSelectionEnabled(true); + table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + TreeTableColumn lastColumn = null; + int id = 1; + + for (int i = 0; i < spec.length;) { + Object x = spec[i++]; + if (x instanceof Cmd cmd) { + switch (cmd) { + case COL: + TreeTableColumn c = new TreeTableColumn<>(); + table.getColumns().add(c); + c.setText("C" + table.getColumns().size()); + c.setCellValueFactory((f) -> new SimpleStringProperty(" ")); + lastColumn = c; + break; + case MAX: + { + int w = (int)(spec[i++]); + lastColumn.setMaxWidth(w); + } + break; + case MIN: + { + int w = (int)(spec[i++]); + lastColumn.setMinWidth(w); + } + break; + case PREF: + { + int w = (int)(spec[i++]); + lastColumn.setPrefWidth(w); + } + break; + case ROWS: + int n = (int)(spec[i++]); + for (int j = 0; j < n; j++) { + table.getRoot().getChildren().add(new TreeItem<>(String.valueOf(n))); + } + break; + case COMBINE: + int ix = (int)(spec[i++]); + int ct = (int)(spec[i++]); + combineColumns(table, ix, ct, id++); + break; + default: + throw new Error("?" + cmd); + } + } else { + throw new Error("?" + x); + } + } + + return table; + } + + protected static void combineColumns(TreeTableView t, int ix, int count, int name) { + TreeTableColumn tc = new TreeTableColumn<>(); + tc.setText("N" + name); + + for (int i = 0; i < count; i++) { + TreeTableColumn c = (TreeTableColumn)t.getColumns().remove(ix); + tc.getColumns().add(c); + } + t.getColumns().add(ix, tc); + } + + /** verify that a custom constrained resize policy can indeed be implemented using public APIs */ + @Test + public void testCanImplementCustomResizePolicy() { + double WIDTH = 15.0; + + // constrained resize policy that simply sets all column widths to WIDTH + class UserPolicy + extends ConstrainedColumnResizeBase + implements Callback { + + @Override + public Boolean call(TreeTableView.ResizeFeatures rf) { + List> columns = rf.getTable().getVisibleLeafColumns(); + int sz = columns.size(); + // new public method getContentWidth() is visible + double w = rf.getContentWidth(); + for (TableColumnBase c: columns) { + // using added public method setColumnWidth() + rf.setColumnWidth(c, WIDTH); + } + return false; + } + } + + Object[] spec = { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL + }; + TreeTableView table = createTable(spec); + + UserPolicy policy = new UserPolicy(); + table.setColumnResizePolicy(policy); + table.setPrefWidth(10); + + // verify the policy is in effect + stageLoader = new StageLoader(new BorderPane(table)); + Toolkit.getToolkit().firePulse(); + + for (TreeTableColumn c: table.getColumns()) { + assertEquals(WIDTH, c.getWidth(), EPSILON); + } + + // resize and check again + table.setPrefWidth(10_000); + Toolkit.getToolkit().firePulse(); + + for (TreeTableColumn c: table.getColumns()) { + assertEquals(WIDTH, c.getWidth(), EPSILON); + } + } + + /** + * Exhausive behavioral test. + * + * Goes through all the policies, all valid combinations of constraints, + * and widths increasing to MAX_WIDTH and back, + * checkint that the initial resize does not violate (min,max) constraints. + */ + //@Test // this test takes too much time! + public void testWidthChange() { + int[] COLUMNS = { + 0, 1, 2, 5 + }; + long start = System.currentTimeMillis(); + for (int numCols: COLUMNS) { + SpecGen gen = new SpecGen(numCols); + while (gen.hasNext()) { + Object[] spec = gen.next(); + TreeTableView table = createTable(spec); + stageLoader = new StageLoader(new BorderPane(table)); + try { + for (int ip = 0; ip < POLICIES.length; ip++) { + Callback policy = createPolicy(ip); + table.setColumnResizePolicy(policy); + for (int width: gen.WIDTHS) { + table.setPrefWidth(width); + Toolkit.getToolkit().firePulse(); + checkInvariants(table); + } + } + } finally { + stageLoader.dispose(); + } + } + } + + System.out.println("elapsed time = " + (System.currentTimeMillis() - start) / 60_000 + " minutes."); + } + + protected static final Object[] POLICIES = { + TreeTableView.CONSTRAINED_RESIZE_POLICY_FLEX_NEXT_COLUMN, + TreeTableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN, + TreeTableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS, + TreeTableView.CONSTRAINED_RESIZE_POLICY_LAST_COLUMN, + TreeTableView.CONSTRAINED_RESIZE_POLICY_NEXT_COLUMN, + TreeTableView.CONSTRAINED_RESIZE_POLICY_SUBSEQUENT_COLUMNS + }; + + protected static Callback createPolicy(int ix) { + return (Callback)POLICIES[ix]; + } +} diff --git a/tests/manual/tester/.classpath b/tests/manual/tester/.classpath new file mode 100644 index 00000000000..e91913200f3 --- /dev/null +++ b/tests/manual/tester/.classpath @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/manual/tester/.project b/tests/manual/tester/.project new file mode 100644 index 00000000000..0c55087603f --- /dev/null +++ b/tests/manual/tester/.project @@ -0,0 +1,17 @@ + + + manualTests-tester + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/tests/manual/tester/.settings/org.eclipse.core.resources.prefs b/tests/manual/tester/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/tests/manual/tester/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/tests/manual/tester/src/com/oracle/javafx/tester/ATableViewResizeTester.java b/tests/manual/tester/src/com/oracle/javafx/tester/ATableViewResizeTester.java new file mode 100644 index 00000000000..83d67996562 --- /dev/null +++ b/tests/manual/tester/src/com/oracle/javafx/tester/ATableViewResizeTester.java @@ -0,0 +1,705 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.javafx.tester; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.EventQueue; +import java.util.List; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTable; +import javax.swing.border.EmptyBorder; +import javax.swing.table.DefaultTableModel; +import javafx.application.Application; +import javafx.beans.property.SimpleStringProperty; +import javafx.embed.swing.SwingNode; +import javafx.geometry.Orientation; +import javafx.scene.Scene; +import javafx.scene.control.ComboBox; +import javafx.scene.control.ConstrainedColumnResizeBase; +import javafx.scene.control.Label; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumnBase; +import javafx.scene.control.TableView; +import javafx.scene.control.TableView.ResizeFeatures; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +import javafx.util.Callback; + +/** + * Tests TableView/JTable constrained column resize modes. + */ +public class ATableViewResizeTester extends Application { + + enum Demo { + PREF("pref only"), + ALL("all set: min, pref, max"), + EMPTY("empty with pref"), + MIN_WIDTH("min width"), + MAX_WIDTH("max width"), + MIN_WIDTH2("min width (middle)"), + MAX_WIDTH2("max width (middle)"), + MIN_WIDTH3("min width (beginning)"), + MAX_WIDTH3("max width (beginning)"), + FIXED_MIDDLE("fixed in the middle"), + ALL_FIXED("all fixed"), + ALL_MAX("all with maximum width"), + MIN_IN_CENTER("min widths set in middle columns"), + MAX_IN_CENTER("max widths set in middle columns"), + NO_NESTED("no nested columns"), + NESTED("nested columns"), + MILLION("million rows"), + MANY_COLUMNS("many columns"), + MANY_COLUMNS_SAME("many columns, same pref"); + + private final String text; + Demo(String text) { this.text = text; } + public String toString() { return text; } + } + + public enum Policy { + AUTO_RESIZE_FLEX_NEXT_COLUMN(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS), + AUTO_RESIZE_FLEX_LAST_COLUMN(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS), + AUTO_RESIZE_NEXT_COLUMN(JTable.AUTO_RESIZE_NEXT_COLUMN), + AUTO_RESIZE_SUBSEQUENT_COLUMNS(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS), + AUTO_RESIZE_LAST_COLUMN(JTable.AUTO_RESIZE_LAST_COLUMN), + AUTO_RESIZE_ALL_COLUMNS(JTable.AUTO_RESIZE_ALL_COLUMNS), + USER_DEFINED_EQUAL_WIDTHS(JTable.AUTO_RESIZE_ALL_COLUMNS), + UNCONSTRAINED_RESIZE_POLICY(JTable.AUTO_RESIZE_OFF), + CONSTRAINED_RESIZE_POLICY(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS); + + private final int value; + Policy(int v) { this.value = v; } + public int getValue() { return value; } + } + + public enum Cmd { + ROWS, + COL, + MIN, + PREF, + MAX, + COMBINE + } + + protected BorderPane contentPane; + protected ComboBox demoSelector; + protected ComboBox policySelector; + protected SwingPanel swingPanel; + protected static Callback, TableCell> CELL_FACTORY = initCellFactory(); + + public static void main(String[] args) { + Application.launch(ATableViewResizeTester.class, args); + } + + @Override + public void start(Stage stage) throws Exception { + contentPane = new BorderPane(); + + // selector + demoSelector = new ComboBox<>(); + demoSelector.getItems().addAll(Demo.values()); + demoSelector.setEditable(false); + demoSelector.getSelectionModel().selectedItemProperty().addListener((s,p,c) -> { + updatePane(); + }); + + policySelector = new ComboBox<>(); + policySelector.getItems().addAll(Policy.values()); + policySelector.setEditable(false); + policySelector.getSelectionModel().selectedItemProperty().addListener((s,p,c) -> { + updatePane(); + }); + + // layout + + SplitPane split = new SplitPane(contentPane, new BorderPane()); + + HBox hb = new HBox( + new Label("Data:"), + demoSelector, + new Label(" Policy:"), + policySelector + ); + hb.setSpacing(5); + + BorderPane bp = new BorderPane(); + bp.setTop(hb); + bp.setCenter(split); + + SwingNode swn = new SwingNode(); + EventQueue.invokeLater(() -> { + swingPanel = new SwingPanel(); + swn.setContent(swingPanel); + }); + + SplitPane sp = new SplitPane(bp, new BorderPane(swn)); + sp.setOrientation(Orientation.VERTICAL); + + stage.setScene(new Scene(sp)); + stage.setWidth(1000); + stage.setHeight(500); + stage.show(); + + stage.renderScaleXProperty().addListener((s,p,c) -> updateTitle(stage)); + updateTitle(stage); + + demoSelector.getSelectionModel(). + selectFirst(); + //select(Demo.FIXED_MIDDLE); + policySelector.getSelectionModel(). + selectFirst(); + //select(Policy.AUTO_RESIZE_SUBSEQUENT_COLUMNS); + } + + protected void updateTitle(Stage s) { + s.setTitle("TableView/JTable Resize Tester " + System.getProperty("java.version") + " scaleX=" + s.getRenderScaleX()); + } + + protected Callback wrap(Callback policy) { + return new Callback() { + @Override + public Boolean call(ResizeFeatures f) { + Boolean rv = policy.call(f); + int ix = f.getTable().getColumns().indexOf(f.getColumn()); + System.out.println( + "col=" + (ix < 0 ? f.getColumn() : ix) + + " delta=" + f.getDelta() + + " w=" + f.getTable().getWidth() + + " rv=" + rv + ); + return rv; + } + }; + } + + protected String describe(TableColumn c) { + StringBuilder sb = new StringBuilder(); + if(c.getMinWidth() != 10.0) { + sb.append("m"); + } + if(c.getPrefWidth() != 80.0) { + sb.append("p"); + } + if(c.getMaxWidth() != 5000.0) { + sb.append("X"); + } + return sb.toString(); + } + + protected Callback createPolicy(Policy p) { + switch(p) { + case AUTO_RESIZE_FLEX_NEXT_COLUMN: + return TableView.CONSTRAINED_RESIZE_POLICY_FLEX_NEXT_COLUMN; + case AUTO_RESIZE_FLEX_LAST_COLUMN: + return TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN; + case AUTO_RESIZE_ALL_COLUMNS: + return TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS; + case AUTO_RESIZE_LAST_COLUMN: + return TableView.CONSTRAINED_RESIZE_POLICY_LAST_COLUMN; + case AUTO_RESIZE_NEXT_COLUMN: + return TableView.CONSTRAINED_RESIZE_POLICY_NEXT_COLUMN; + case AUTO_RESIZE_SUBSEQUENT_COLUMNS: + return TableView.CONSTRAINED_RESIZE_POLICY_SUBSEQUENT_COLUMNS; + case CONSTRAINED_RESIZE_POLICY: + return TableView.CONSTRAINED_RESIZE_POLICY; + case UNCONSTRAINED_RESIZE_POLICY: + return TableView.UNCONSTRAINED_RESIZE_POLICY; + case USER_DEFINED_EQUAL_WIDTHS: + return new UserDefinedResizePolicy(); + default: + throw new Error("?" + p); + } + } + + protected Object[] createSpec(Demo d) { + switch(d) { + case ALL: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, Cmd.MIN, 20, Cmd.PREF, 20, Cmd.MAX, 20, + Cmd.COL, Cmd.PREF, 200, + Cmd.COL, Cmd.PREF, 300, Cmd.MAX, 400, + Cmd.COL + }; + case PREF: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, Cmd.PREF, 100, + Cmd.COL, Cmd.PREF, 200, + Cmd.COL, Cmd.PREF, 300, + Cmd.COL, Cmd.PREF, 400 + }; + case EMPTY: + return new Object[] { + Cmd.COL, Cmd.PREF, 100, + Cmd.COL, Cmd.PREF, 200, + Cmd.COL, Cmd.PREF, 300 + }; + case MIN_WIDTH: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, Cmd.MIN, 300 + }; + case MAX_WIDTH: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, Cmd.MAX, 100 + }; + case MIN_WIDTH2: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, + Cmd.COL, Cmd.MIN, 300, + Cmd.COL + }; + case MAX_WIDTH2: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, + Cmd.COL, Cmd.MAX, 100, + Cmd.COL + }; + case MIN_WIDTH3: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, Cmd.MIN, 300, + Cmd.COL, + Cmd.COL, + Cmd.COL + }; + case MAX_WIDTH3: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, Cmd.MAX, 100, + Cmd.COL, + Cmd.COL, + Cmd.COL + }; + case MIN_IN_CENTER: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, Cmd.MIN, 20, + Cmd.COL, Cmd.MIN, 30, + Cmd.COL, Cmd.MIN, 40, + Cmd.COL, Cmd.MIN, 50, + Cmd.COL, Cmd.MIN, 60, + Cmd.COL + }; + case MAX_IN_CENTER: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, Cmd.MAX, 20, + Cmd.COL, Cmd.MAX, 30, + Cmd.COL, Cmd.MAX, 40, + Cmd.COL, Cmd.MAX, 50, + Cmd.COL, Cmd.MAX, 60, + Cmd.COL + }; + case FIXED_MIDDLE: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, Cmd.MIN, 100, Cmd.MAX, 100, + Cmd.COL, Cmd.MIN, 100, Cmd.MAX, 100, + Cmd.COL, + Cmd.COL, + Cmd.COL + }; + case ALL_FIXED: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, Cmd.MIN, 50, Cmd.MAX, 50, + Cmd.COL, Cmd.MIN, 50, Cmd.MAX, 50, + Cmd.COL, Cmd.MIN, 50, Cmd.MAX, 50 + }; + case ALL_MAX: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, Cmd.MAX, 50, + Cmd.COL, Cmd.MAX, 50, + Cmd.COL, Cmd.MAX, 50 + }; + case NO_NESTED: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, Cmd.PREF, 100, + Cmd.COL, Cmd.PREF, 200, + Cmd.COL, Cmd.PREF, 300, + Cmd.COL, Cmd.MIN, 100, Cmd.MAX, 100, + Cmd.COL, Cmd.PREF, 100, + Cmd.COL, Cmd.MIN, 100, + Cmd.COL, Cmd.MAX, 100, + Cmd.COL, Cmd.PREF, 400, + Cmd.COL + }; + case NESTED: + return new Object[] { + Cmd.ROWS, 3, + Cmd.COL, Cmd.PREF, 100, + Cmd.COL, Cmd.PREF, 200, + Cmd.COL, Cmd.PREF, 300, + Cmd.COL, Cmd.MIN, 100, Cmd.MAX, 100, + Cmd.COL, Cmd.PREF, 100, + Cmd.COL, Cmd.MIN, 100, + Cmd.COL, Cmd.MAX, 100, + Cmd.COL, Cmd.PREF, 400, + Cmd.COL, + Cmd.COMBINE, 0, 3, + Cmd.COMBINE, 1, 2 + }; + case MANY_COLUMNS: + return new Object[] { + Cmd.ROWS, 300, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL, + Cmd.COL + }; + case MANY_COLUMNS_SAME: + return new Object[] { + Cmd.ROWS, 300, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30, + Cmd.COL, Cmd.PREF, 30 + }; + case MILLION: + return new Object[] { + Cmd.ROWS, 1_000_000, + Cmd.COL, + Cmd.COL + }; + default: + throw new Error("?" + d); + } + } + + protected void updatePane() { + Policy p = policySelector.getSelectionModel().getSelectedItem(); + Demo d = demoSelector.getSelectionModel().getSelectedItem(); + Object[] spec = createSpec(d); + + Pane n = createPane(p, spec); + contentPane.setCenter(n); + + EventQueue.invokeLater(() -> { + swingPanel.updatePane(p, spec); + }); + } + + protected void combineColumns(TableView t, int ix, int count, int name) { + TableColumn tc = new TableColumn<>(); + tc.setCellFactory(CELL_FACTORY); + tc.setText("N" + name); + + for (int i = 0; i < count; i++) { + TableColumn c = t.getColumns().remove(ix); + tc.getColumns().add(c); + } + t.getColumns().add(ix, tc); + } + + protected Pane createPane(Policy policy, Object[] spec) { + if ((spec == null) || (policy == null)) { + return new BorderPane(); + } + + TableView table = new TableView<>(); + table.getSelectionModel().setCellSelectionEnabled(true); + table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + Callback p = createPolicy(policy); + table.setColumnResizePolicy(p); + + TableColumn lastColumn = null; + int id = 1; + + for (int i = 0; i < spec.length;) { + Object x = spec[i++]; + if (x instanceof Cmd cmd) { + switch (cmd) { + case COL: + TableColumn c = new TableColumn<>(); + c.setCellFactory(CELL_FACTORY); + table.getColumns().add(c); + c.setText("C" + table.getColumns().size()); +// if (table.getColumns().size() == 1) { +// c.setText("Really really really really really really really really really really really really really long"); +// } + c.setCellValueFactory((f) -> new SimpleStringProperty(describe(c))); + lastColumn = c; + break; + case MAX: { + int w = (int)(spec[i++]); + lastColumn.setMaxWidth(w); + } + break; + case MIN: { + int w = (int)(spec[i++]); + lastColumn.setMinWidth(w); + } + break; + case PREF: { + int w = (int)(spec[i++]); + lastColumn.setPrefWidth(w); + } + break; + case ROWS: + int n = (int)(spec[i++]); + for (int j = 0; j < n; j++) { + table.getItems().add(String.valueOf(n)); + } + break; + case COMBINE: + int ix = (int)(spec[i++]); + int ct = (int)(spec[i++]); + combineColumns(table, ix, ct, id++); + break; + default: + throw new Error("?" + cmd); + } + } else { + throw new Error("?" + x); + } + } + + BorderPane bp = new BorderPane(); + bp.setCenter(table); + return bp; + } + + protected static class SwingPanel extends JPanel { + public SwingPanel() { + super(new BorderLayout()); + } + + public void updatePane(Policy policy, Object[] spec) { + JComponent p = createPanel(policy, spec); + removeAll(); + if (p != null) { + add(p); + } + validate(); + repaint(); + } + + private int createHSBPolicy(Policy p) { + switch (p) { + case UNCONSTRAINED_RESIZE_POLICY: + return JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED; + default: + return JScrollPane.HORIZONTAL_SCROLLBAR_NEVER; + } + } + + protected JComponent createPanel(Policy policy, Object[] spec) { + if ((spec == null) || (policy == null)) { + return null; + } + + DefaultTableModel m = new DefaultTableModel() { + @Override + public Object getValueAt(int row, int column) { + return ""; + } + }; + + JTable t = new JTable(m); + t.setShowHorizontalLines(true); + t.setShowVerticalLines(true); + t.setGridColor(Color.LIGHT_GRAY); + t.setAutoResizeMode(policy.getValue()); + + javax.swing.table.TableColumn lastColumn = null; + + for (int i = 0; i < spec.length;) { + Object x = spec[i++]; + if (x instanceof Cmd cmd) { + switch (cmd) { + case COL: + javax.swing.table.TableColumn c = new javax.swing.table.TableColumn(); + t.getColumnModel().addColumn(c); + c.setHeaderValue("C" + t.getColumnCount()); + lastColumn = c; + break; + case MAX: { + int w = (int)(spec[i++]); + lastColumn.setMaxWidth(w); + } + break; + case MIN: { + int w = (int)(spec[i++]); + lastColumn.setMinWidth(w); + } + break; + case PREF: { + int w = (int)(spec[i++]); + lastColumn.setPreferredWidth(w); + } + break; + case ROWS: + int n = (int)(spec[i++]); + for (int j = 0; j < n; j++) { + m.addRow((Object[])null); + } + break; + case COMBINE: + // ignored + int ix = (int)(spec[i++]); + int ct = (int)(spec[i++]); + break; + default: + throw new Error("?" + cmd); + } + } else { + throw new Error("?" + x); + } + } + + EmptyBorder b = new EmptyBorder(0, 0, 0, 0); + + int hsp = createHSBPolicy(policy); + JScrollPane scroll = new JScrollPane(t, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, hsp); + scroll.setBorder(b); + + JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, scroll, new JPanel()); + split.setContinuousLayout(true); + return split; + } + } + + private static Callback, TableCell> initCellFactory() { + return new Callback, TableCell>() { + public TableCell call(TableColumn column) { + return new TableCell() { + { + widthProperty().addListener((s, p, c) -> { + setStyle(computeStyle()); + }); + } + + @Override + public void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + setText(item); + } + + private String computeStyle() { + boolean empty = isEmpty(); + if (!empty) { + double w = getWidth(); + if (isClose(w, column.getMinWidth())) { + return "-fx-background-color:#ffeeee;"; + } else if (isClose(w, column.getPrefWidth())) { + return "-fx-background-color:#eeffee;"; + } else if (isClose(w, column.getMaxWidth())) { + return "-fx-background-color:#eeeeff;"; + } + } + return null; + } + }; + } + }; + } + + private static boolean isClose(double a, double b) { + return Math.abs(a - b) < 0.00001; + } + + /** + * a user-defined policy demonstrates that we can indeed create a custom policy + * using the new API. this policy simply sizes all columns equally. + */ + protected static class UserDefinedResizePolicy + extends ConstrainedColumnResizeBase + implements Callback { + + @SuppressWarnings("unchecked") + @Override + public Boolean call(ResizeFeatures rf) { + List> visibleLeafColumns = rf.getTable().getVisibleLeafColumns(); + int sz = visibleLeafColumns.size(); + // using added public method getContentWidth() + double w = rf.getContentWidth() / sz; + for (TableColumnBase c: visibleLeafColumns) { + // using added public method setColumnWidth() + rf.setColumnWidth(c, w); + } + return false; + } + } +} diff --git a/tests/manual/tester/src/module-info.java b/tests/manual/tester/src/module-info.java new file mode 100644 index 00000000000..bb019dc0113 --- /dev/null +++ b/tests/manual/tester/src/module-info.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Provides manual test applications. + */ +module test.manual { + exports com.oracle.javafx.tester; + + requires javafx.base; + requires javafx.controls; + requires javafx.graphics; + requires java.desktop; + requires javafx.swing; +}