All Downloads are FREE. Search and download functionalities are using the official Maven repository.

impl.org.controlsfx.spreadsheet.GridVirtualFlow Maven / Gradle / Ivy

Go to download

High quality UI controls and other tools to complement the core JavaFX distribution

There is a newer version: 9.0.0
Show newest version
/**
 * Copyright (c) 2013, 2016 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.spreadsheet;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import javafx.beans.Observable;
import javafx.beans.binding.When;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableRow;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Scale;
import org.controlsfx.control.spreadsheet.SpreadsheetCell;
import org.controlsfx.control.spreadsheet.SpreadsheetView;

final class GridVirtualFlow> extends VirtualFlow {
    
    /**
     * 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.. */ private static final Comparator ROWCMP = new Comparator() { @Override public int compare(GridRow firstRow, GridRow secondRow) { //o1.getIndex() < o2.getIndex() ? -1 : +1; return secondRow.getIndex() - firstRow.getIndex(); } }; /*************************************************************************** * * Private Fields * * **************************************************************************/ private SpreadsheetView spreadSheetView; private final GridViewSkin gridViewSkin; /** * 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 StackPane corner; private Scale scale; /*************************************************************************** * * Constructor * * **************************************************************************/ public GridVirtualFlow(GridViewSkin gridViewSkin) { super(); this.gridViewSkin = gridViewSkin; final ChangeListener listenerY = new ChangeListener() { @Override public void changed(ObservableValue ov, Number t, Number t1) { layoutTotal(); } }; getVbar().valueProperty().addListener(listenerY); getHbar().valueProperty().addListener(hBarValueChangeListener); widthProperty().addListener(hBarValueChangeListener); sheetChildren = findSheetChildren(); findCorner(); //When we click outside of the grid, we want to deselect all cells. addEventHandler(MouseEvent.MOUSE_PRESSED, (MouseEvent event) -> { if (event.getTarget().getClass() == GridRow.class) { spreadSheetView.getSelectionModel().clearSelection(); } }); } /*************************************************************************** * * Public Methods * * **************************************************************************/ public void init(SpreadsheetView spv) { /** * The idea is to work-around * https://javafx-jira.kenai.com/browse/RT-36396 in order to have the * same behavior between the vertical scrollBar and the horizontal * scrollBar. */ getHbar().maxProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Number oldValue, Number newValue) { //We want to go page by page. getHbar().setBlockIncrement(getWidth()); getHbar().setUnitIncrement(newValue.doubleValue()/20); } }); scale = new Scale(1 / spv.getZoomFactor(), 1 / spv.getZoomFactor()); scale.setPivotX(getHbar().getWidth() / 2); getHbar().getTransforms().add(scale); getVbar().getTransforms().add(scale); corner.getTransforms().add(scale); this.spreadSheetView = spv; spreadSheetView.zoomFactorProperty().addListener((ObservableValue observable, Number oldValue, Number newValue) -> { scale.setX(1 / newValue.doubleValue()); scale.setY(1 / newValue.doubleValue()); }); //We clip the rectangle selection with a rectangle, inception style. Rectangle rec = new Rectangle(); rec.widthProperty().bind(widthProperty().subtract(new When(getVbar().visibleProperty()).then(getVbar().widthProperty()).otherwise(0))); rec.heightProperty().bind(heightProperty().subtract(new When(getHbar().visibleProperty()).then(getHbar().heightProperty()).otherwise(0))); gridViewSkin.rectangleSelection.setClip(rec); getChildren().add(gridViewSkin.rectangleSelection); spv.getFixedRows().addListener((Observable observable) -> { List toRemove = new ArrayList<>(); for (T cell : myFixedCells) { if (!spv.getFixedRows().contains(spreadSheetView.getFilteredSourceIndex(cell.getIndex()))) { cell.setManaged(false); cell.setVisible(false); toRemove.add(cell); } } myFixedCells.removeAll(toRemove); }); } // @Override // public void show(int index) { // super.show(index); // layoutTotal(); // layoutFixedRows(); // } @Override public void scrollTo(int index) { //If we have some fixedRows, we check if the selected row is not below them if (!getCells().isEmpty() && !VerticalHeader.isFixedRowEmpty(spreadSheetView)) { double offset = gridViewSkin.getFixedRowHeight(); while (offset >= 0 && index > 0) { index--; offset -= gridViewSkin.getRowHeight(index); } } super.scrollTo(index); layoutTotal(); layoutFixedRows(); } @Override public double scrollPixels(final double delta) { final double returnValue = super.scrollPixels(delta); layoutTotal(); layoutFixedRows(); return returnValue; } List getFixedCells(){ return myFixedCells; } /*************************************************************************** * * Protected Methods * * **************************************************************************/ /** * We need to return here the very top row in term of "z-order". Because we * will add in this row the TableCell that are in fixedColumn and which * needs to be drawn on top of all others. * * @return */ GridRow getTopRow() { if (!sheetChildren.isEmpty()) { /** * When scrolling with mouse wheel, some row are present but will * not be lay out. Thus we only consider the row with children as * really available. */ try { int i = sheetChildren.size() - 1; while (i >= sheetChildren.size() || (((GridRow) sheetChildren.get(i)).getChildrenUnmodifiable().isEmpty() && i > 0)) { --i; } return (GridRow) sheetChildren.get(i); //We may have some IndexOutOfBoundsException sometimes... } catch (Exception ex) { return null; } } return null; } @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 (spreadSheetView != null /*&& (spreadSheetView.getEditingCell() == null || spreadSheetView .getEditingCell().getRow() == -1)*/) { 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()); } } /** * If we have modify the Scale, the scrollBars will be smaller or * bigger. But we want to have them the same size as before, so we * reverse the effect of the scale applied, and place the bar to the * proper space. */ Pos pos = Pos.TOP_LEFT; double width = getWidth(); double height = getHeight(); double top = getInsets().getTop(); double right = getInsets().getRight(); double left = getInsets().getLeft(); double bottom = getInsets().getBottom(); double scaleX = scale.getX(); double shift = 1 - scaleX; double contentWidth = (width / scaleX) - left - right - getVbar().getWidth(); double contentHeight = (height / scaleX) - top - bottom - getHbar().getHeight(); //HBAR /** * Magic numbers coming out of nowhere but I don't understand why * the bar are shifting away when zooming... */ layoutInArea(getHbar(), 0 - shift * 10, height - (getHbar().getHeight() * scaleX), contentWidth, contentHeight, 0, null, pos.getHpos(), pos.getVpos()); //VBAR layoutInArea(getVbar(), width - getVbar().getWidth() + shift, 0, contentWidth, contentHeight, 0, null, pos.getHpos(), pos.getVpos()); //CORNER if (corner != null) { layoutInArea(corner, width - getVbar().getWidth() + shift, getHeight() - (getHbar().getHeight() * scaleX), corner.getWidth(), corner.getHeight(), 0, null, pos.getHpos(), pos.getVpos()); } } /** * 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 (GridRow cell : (List)getCells()) { if (cell != null && cell.getIndex() >= 0 && (!gridViewSkin.hBarValue.get(cell.getIndex()) || gridViewSkin.rowToLayout.get(cell.getIndex()))) { cell.requestLayout(); } } } /** * 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. */ private void removeDeportedCells() { ArrayList rowToRemove = new ArrayList<>(); for (Entry> entry : gridViewSkin.deportedCells.entrySet()) { ArrayList toRemove = new ArrayList<>(); for (CellView 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(cell.getTableRow())) { entry.getKey().removeCell(cell); toRemove.add(cell); } } entry.getValue().removeAll(toRemove); if (entry.getValue().isEmpty()) { rowToRemove.add(entry.getKey()); } } for (GridRow row : rowToRemove) { gridViewSkin.deportedCells.remove(row); } } protected ScrollBar getVerticalBar() { return getVbar(); } protected ScrollBar getHorizontalBar() { return getHbar(); } @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<>(); } /** * WARNING : This is bad but no other options right now. This will find the * corner where the two scrollBars are joigning. */ private void findCorner() { if (!getChildren().isEmpty()) { for (Node node : getChildren()) { if (node instanceof StackPane) { corner = (StackPane) node; } } } } /** * 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 (!VerticalHeader.isFixedRowEmpty(spreadSheetView) && 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 = spreadSheetView.getFixedRows().size() - 1; i >= 0; i--) { fixedRowIndex = spreadSheetView.getFixedRows().get(i); if(spreadSheetView.isRowHidden(i)){ continue; } //Changing the index to viewRow. fixedRowIndex = spreadSheetView.getFilteredRow(fixedRowIndex); 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); 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); sheetChildren.remove(row); } /** * OLD COMMENT : We must push to Front only if the row * is at the very top and has a risk to be recovered. * This is happening only if this row is translated. * * NEW COMMENT: I'm not sure about this.. Since the * fixedColumn are not in the special top row, we don't * care if the row is pushed to front.. need * investigation */ 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); } /** * Sometime, when we set a new Grid on a SpreadsheetView without recreating it, * we can end up with some rows not being added to the ViewPort. * So we must be sure it's in and add it ourself otherwise. */ if(!sheetChildren.contains(row)){ sheetChildren.add(row); } row.setManaged(true); row.setVisible(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 = (List) getCells(); final List tset = new ArrayList<>(temp); Collections.sort(tset, ROWCMP); for (final TableRow> r : tset) { r.toFront(); } } private final ChangeListener hBarValueChangeListener = new ChangeListener() { @Override public void changed(ObservableValue ov, Number t, Number t1) { gridViewSkin.hBarValue.clear(); } }; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy