
impl.org.controlsfx.tableview2.TableView2VirtualFlow Maven / Gradle / Ivy
/**
* Copyright (c) 2013, 2020 ControlsFX
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package impl.org.controlsfx.tableview2;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableCell;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.layout.Region;
import org.controlsfx.control.tableview2.TableView2;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
final class TableView2VirtualFlow> extends VirtualFlow {
/***************************************************************************
* * Private Fields * *
**************************************************************************/
private final TableView2> tableView;
private final TableView2Skin> skin;
/**
* Store the fixedRow in order to place them at the top when necessary.
* That is to say, when the VirtualFlow has not already placed one.
*/
private final ArrayList myFixedCells = new ArrayList<>();
public final List sheetChildren;
private boolean adjusting;
private final DoubleProperty adjustedPixels = new SimpleDoubleProperty() {
@Override
protected void invalidated() {
if (! adjusting && get() > 0 && myFixedCells.size() < tableView.getFixedRows().size()) {
/**
* Force layout pass in the inner (outer) tableView, to avoid flickering
* under a fast initial scrolling in the outer (inner) tableView,
*/
layoutChildren();
}
adjusting = false;
}
};
/***************************************************************************
* * Constructor * *
**************************************************************************/
public TableView2VirtualFlow(TableView2Skin> skin) {
super();
this.skin = skin;
this.tableView = (TableView2>) skin.getSkinnable();
final ChangeListener listenerY = (ov, t, t1) -> layoutTotal();
getVbar().valueProperty().addListener(listenerY);
getHbar().valueProperty().addListener(hBarValueChangeListener);
widthProperty().addListener(hBarValueChangeListener);
sheetChildren = findSheetChildren();
}
/***************************************************************************
* * Public Methods * *
**************************************************************************/
public void init() {
/**
* The idea is to work-around
* https://bugs.openjdk.java.net/browse/JDK-8090224 in order to have the
* same behavior between the vertical scrollBar and the horizontal
* scrollBar.
*/
getHbar().maxProperty().addListener((observable, oldValue, newValue) -> {
//We want to go page by page.
getHbar().setBlockIncrement(getWidth());
getHbar().setUnitIncrement(newValue.doubleValue()/20);
});
tableView.getFixedRows().addListener((Observable observable) -> {
List toRemove = new ArrayList<>();
for (T cell : myFixedCells) {
if (!tableView.getFixedRows().contains(cell.getIndex())) {
cell.setManaged(false);
cell.setVisible(false);
toRemove.add(cell);
}
}
myFixedCells.removeAll(toRemove);
});
}
/** {@inheritDoc} */
@Override public void scrollTo(int index) {
//If we have some fixedRows, we check if the selected row is not below them
if (! getCells().isEmpty() && ! tableView.getFixedRows().isEmpty()) {
double offset = skin.getFixedRowHeight();
while (offset >= 0 && index > 0) {
index--;
offset -= skin.getRowHeight(index);
}
}
super.scrollTo(index);
layoutTotal();
layoutFixedRows();
}
/** {@inheritDoc} */
@Override public double scrollPixels(final double delta) {
final double returnValue = super.scrollPixels(delta);
adjusting = true;
this.adjustedPixels.set(delta);
layoutTotal();
layoutFixedRows();
return returnValue;
}
List getFixedCells() {
return myFixedCells;
}
// clears the list of fixed cells. On the next layout pass the list is built
// again
void rebuildFixedCells() {
myFixedCells.clear();
}
// double property with delta pixels adjusted while scrolling
DoubleProperty adjustedPixelsProperty() {
return adjustedPixels;
}
/***************************************************************************
* * Protected Methods * *
**************************************************************************/
/** {@inheritDoc} */
@Override protected void layoutChildren() {
/**
* In fact, we must do a layout even when editing, because if the user
* resize the window during edition, if we block layout, the view will
* be in a wrong state.
*/
if (tableView != null) {
sortRows();
super.layoutChildren();
layoutTotal();
layoutFixedRows();
/**
* Sometimes, the visible amount is not computed when we have few
* big rows. If we detect that case, we must compute it manually
* otherwise the Vbar is wrongly set.
*/
if (getVbar().getVisibleAmount() == 0.0
&& getVbar().isVisible()
&& getCells().size() != getCellCount()) {
getVbar().setMax(1);
getVbar().setVisibleAmount(getCells().size() / (float) getCellCount());
}
}
}
/**
* Layout all the visible rows
*/
protected void layoutTotal() {
sortRows();
removeDeportedCells();
// When scrolling fast with fixed Rows, cells is empty and not recreated..
if (getCells().isEmpty()) {
reconfigureCells();
}
for (T cell : getCells()) {
if (cell == null) {
continue;
}
cell.getProperties().put("fixed", false);
if (cell.getIndex() >= 0 &&
(!skin.hBarValue.get(cell.getIndex()) || skin.rowToLayout.get(cell.getIndex()))) {
if (skin.rowToLayout.get(cell.getIndex())) {
cell.getProperties().put("fixed", true);
}
cell.requestLayout();
}
}
}
private void removeDeportedCells() {
/**
* When we layout, we also remove the cell that have been deported into
* other rows in order not to have some TableCell hanging out.
*
* When scrolling with mouse wheel, we will request the layout of all
* rows, but only one row will be really called. Thus by wiping entirely
* the deportedCell, all cells in fixedColumns are gone. So we must be
* smarter.
*/
ArrayList> rowToRemove = new ArrayList<>();
for (Object o : skin.deportedCells.entrySet()) {
Entry, Set>> entry = (Entry, Set>>) o;
ArrayList> toRemove = new ArrayList<>();
for (TableCell cell : entry.getValue()) {
//If we're not editing and the TableRow of the cell is not contained anymore, we remove.
if (!cell.isEditing() && !getCells().contains((T) cell.getTableRow())) {
entry.getKey().removeCell(cell);
toRemove.add((TableCell) cell);
}
}
entry.getValue().removeAll(toRemove);
if (entry.getValue().isEmpty()) {
rowToRemove.add(entry.getKey());
}
}
for (TableRow2 row : rowToRemove) {
skin.deportedCells.remove(row);
}
}
protected ScrollBar getVerticalBar() {
return getVbar();
}
protected ScrollBar getHorizontalBar() {
return getHbar();
}
/** {@inheritDoc} */
@Override protected List getCells() {
return super.getCells();
}
/***************************************************************************
* * Private Methods * *
**************************************************************************/
/**
* WARNING : This is bad but no other options right now. This will find the
* sheetChildren of the VirtualFlow, aka where the cells are kept and
* clipped. See layoutFixedRows() or getTopRow() for use.
*
* @return
*/
private List findSheetChildren(){
if(!getChildren().isEmpty()){
if(getChildren().get(0) instanceof Region){
Region region = (Region) getChildren().get(0);
if(!region.getChildrenUnmodifiable().isEmpty()){
if(region.getChildrenUnmodifiable().get(0) instanceof Group){
return ((Group)region.getChildrenUnmodifiable().get(0)).getChildren();
}
}
}
}
return new ArrayList<>();
}
/**
* Layout the fixed rows to position them correctly
*/
private void layoutFixedRows() {
//We must have a cell in ViewPort because otherwise
//we short-circuit the VirtualFlow.
if (! tableView.getFixedRows().isEmpty() && tableView.isRowFixingEnabled() && getFirstVisibleCellWithinViewport() != null) {
sortRows();
/**
* What I do is just going after the VirtualFlow in order to ADD
* (not replace like before) new rows at the top.
*
* If the VirtualFlow has the row, then I will hide mine and let him
* handle. But if the row is missing, then I must show mine in order
* to have the fixed row.
*/
T row = null;
Integer fixedRowIndex;
rows:
for (int i = tableView.getFixedRows().size() - 1; i >= 0; i--) {
fixedRowIndex = tableView.getFixedRows().get(i);
T lastCell = getLastVisibleCellWithinViewport();
//If the fixed row is out of bounds
if (lastCell != null && fixedRowIndex > lastCell.getIndex()) {
if (row != null) {
row.setVisible(false);
row.setManaged(false);
row.getProperties().put("fixed", false);
sheetChildren.remove(row);
}
continue;
}
//We see if the row is laid out by the VirtualFlow
for (T virtualFlowCells : getCells()) {
if (virtualFlowCells.getIndex() > fixedRowIndex) {
break;
} else if (virtualFlowCells.getIndex() == fixedRowIndex) {
row = containsRows(fixedRowIndex);
if (row != null) {
row.setVisible(false);
row.setManaged(false);
row.getProperties().put("fixed", false);
sheetChildren.remove(row);
}
virtualFlowCells.toFront();
continue rows;
}
}
row = containsRows(fixedRowIndex);
if (row == null) {
/**
* getAvailableCell is not added our cell to the ViewPort in some cases.
* So we need to instantiate it ourselves.
*/
row = getCellFactory().call(this);
row.getProperties().put("newcell", null); //$NON-NLS-1$
setCellIndex(row, fixedRowIndex);
resizeCell(row);
myFixedCells.add(row);
}
if (! sheetChildren.contains(row)) {
sheetChildren.add(row);
}
row.setManaged(true);
row.setVisible(true);
row.getProperties().put("fixed", true);
row.toFront();
row.requestLayout();
}
}
}
/**
* Verify if the row has been added to myFixedCell
*
* @param i
* @return
*/
private T containsRows(int i) {
for (T cell : myFixedCells) {
if (cell.getIndex() == i) {
return cell;
}
}
return null;
}
/**
* Sort the rows so that they stay in order for layout
*/
private void sortRows() {
final List temp = getCells();
final List tset = new ArrayList<>(temp);
/**
* With that comparator we can lay out our rows in the reverse order. That
* is to say from the bottom to the very top. In that manner we are sure
* that our spanning cells will COVER the cell below so we don't have any
* problems with missing hovering, the editor jammed etc.
*
*
* The only problem is for the fixed column but the {@link #getTopRow(int) }
* now returns the very first row and allow us to put some privileged
* TableCell in it if they feel the need to be on top in term of z-order.
*
* FIXME The best would be to put a TreeList of something like that in order
* not to sort the rows everytime, need investigation..
*/
Collections.sort(tset, (i1, i2) -> i2.getIndex() - i1.getIndex());
for (final T r : tset) {
r.toFront();
}
}
private final ChangeListener hBarValueChangeListener = new ChangeListener() {
@Override
public void changed(ObservableValue extends Number> ov, Number t, Number t1) {
skin.hBarValue.clear();
}
};
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy