Java examples for JavaFX:Table
The constrained resize algorithm used by JavaFX TableView and TreeTableView.
/*/*from w w w . j a v a 2 s . c o m*/ * Copyright (c) 2012, 2013, 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. */ import java.util.ArrayList; import java.util.Collections; import java.util.List; import javafx.beans.InvalidationListener; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; public class Main{ /** * 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<? extends TableColumnBase<?, ?>> 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) > 1) { 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) < .0000001) { 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<? extends TableColumnBase<?, ?>> 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<? extends TableColumnBase> 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; } static double resize(TableColumnBase column, double delta) { if (delta == 0) return 0.0F; if (!column.isResizable()) return delta; final boolean isShrinking = delta < 0; final List<TableColumnBase<?, ?>> resizingChildren = getResizableChildren( column, isShrinking); if (resizingChildren.size() > 0) { return resizeColumns(resizingChildren, delta); } else { double newWidth = column.getWidth() + delta; if (newWidth > column.getMaxWidth()) { column.impl_setWidth(column.getMaxWidth()); return newWidth - column.getMaxWidth(); } else if (newWidth < column.getMinWidth()) { column.impl_setWidth(column.getMinWidth()); return newWidth - column.getMinWidth(); } else { column.impl_setWidth(newWidth); return 0.0F; } } } private static double resizeColumns( List<? extends TableColumnBase<?, ?>> columns, double delta) { // distribute space between all visible children who can be resized. // To do this we need to work out if we're shrinking or growing the // children, and then which children can be resized based on their // min/pref/max/fixed properties. The results of this are in the // resizingChildren observableArrayList above. final int columnCount = columns.size(); // work out how much of the delta we should give to each child. It should // be an equal amount (at present), although perhaps we'll allow for // functions to calculate this at a later date. double colDelta = delta / columnCount; // we maintain a count of the amount of delta remaining to ensure that // the column resize operation accurately reflects the location of the // mouse pointer. Every time this value is not 0, the UI is a teeny bit // more inaccurate whilst the user continues to resize. double remainingDelta = delta; // We maintain a count of the current column that we're on in case we // need to redistribute the remainingDelta among remaining sibling. int col = 0; // This is a bit hacky - often times the leftOverDelta is zero, but // remainingDelta doesn't quite get down to 0. In these instances we // short-circuit and just return 0.0. boolean isClean = true; for (TableColumnBase<?, ?> childCol : columns) { col++; // resize each child column double leftOverDelta = resize(childCol, colDelta); // calculate the remaining delta if the was anything left over in // the last resize operation remainingDelta = remainingDelta - colDelta + leftOverDelta; // println("\tResized {childCol.text} with {colDelta}, but {leftOverDelta} was left over. RemainingDelta is now {remainingDelta}"); if (leftOverDelta != 0) { isClean = false; // and recalculate the distribution of the remaining delta for // the remaining siblings. colDelta = remainingDelta / (columnCount - col); } } // see isClean above for why this is done return isClean ? 0.0 : remainingDelta; } private static List<TableColumnBase<?, ?>> getResizableChildren( TableColumnBase<?, ?> column, boolean isShrinking) { if (column == null || column.getColumns().isEmpty()) { return Collections.emptyList(); } List<TableColumnBase<?, ?>> tablecolumns = new ArrayList<TableColumnBase<?, ?>>(); for (TableColumnBase c : column.getColumns()) { if (!c.isVisible()) continue; if (!c.isResizable()) continue; if (isShrinking && c.getWidth() > c.getMinWidth()) { tablecolumns.add(c); } else if (!isShrinking && c.getWidth() < c.getMaxWidth()) { tablecolumns.add(c); } } return tablecolumns; } }