impl.org.controlsfx.spreadsheet.GridRowSkin Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of controlsfx Show documentation
Show all versions of controlsfx Show documentation
High quality UI controls and other tools to complement the core JavaFX distribution
/**
* Copyright (c) 2013, 2015 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 com.sun.javafx.scene.control.behavior.CellBehaviorBase;
import com.sun.javafx.scene.control.behavior.TableRowBehavior;
import com.sun.javafx.scene.control.skin.CellSkinBase;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumnBase;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableRow;
import org.controlsfx.control.spreadsheet.Grid;
import org.controlsfx.control.spreadsheet.SpreadsheetCell;
import org.controlsfx.control.spreadsheet.SpreadsheetColumn;
import org.controlsfx.control.spreadsheet.SpreadsheetView;
public class GridRowSkin extends CellSkinBase>, CellBehaviorBase>>> {
private final SpreadsheetHandle handle;
private final SpreadsheetView spreadsheetView;
private Reference> cellsMap;
private final List cells = new ArrayList<>();
public GridRowSkin(SpreadsheetHandle handle, TableRow> gridRow) {
super(gridRow, new TableRowBehavior<>(gridRow));
this.handle = handle;
spreadsheetView = handle.getView();
getSkinnable().setPickOnBounds(false);
registerChangeListener(gridRow.itemProperty(), "ITEM");
registerChangeListener(gridRow.indexProperty(), "INDEX");
}
@Override
protected void handleControlPropertyChanged(String p) {
super.handleControlPropertyChanged(p);
if ("INDEX".equals(p)) {
// Fix for RT-36661, where empty table cells were showing content, as they
// had incorrect table cell indices (but the table row index was correct).
// Note that we only do the update on empty cells to avoid the issue
// noted below in requestCellUpdate().
if (getSkinnable().isEmpty()) {
requestCellUpdate();
}
} else if ("ITEM".equals(p)) {
requestCellUpdate();
} else if ("FIXED_CELL_SIZE".equals(p)) {
// fixedCellSize = fixedCellSizeProperty().get();
// fixedCellSizeEnabled = fixedCellSize > 0;
}
}
private void requestCellUpdate() {
getSkinnable().requestLayout();
// update the index of all children cells (RT-29849).
// Note that we do this after the TableRow item has been updated,
// rather than when the TableRow index has changed (as this will be
// before the row has updated its item). This will result in the
// issue highlighted in RT-33602, where the table cell had the correct
// item whilst the row had the old item.
final int newIndex = getSkinnable().getIndex();
/**
* When the index is changing, we need to clear out all the children
* because we may end up with useless cell in the row.
*/
getChildren().clear();
for (int i = 0, max = cells.size(); i < max; i++) {
cells.get(i).updateIndex(newIndex);
}
}
@Override
protected void layoutChildren(double x, final double y, final double w, final double h) {
final ObservableList extends TableColumnBase, ?>> visibleLeafColumns = handle.getGridView().getVisibleLeafColumns();
if (visibleLeafColumns.isEmpty()) {
super.layoutChildren(x, y, w, h);
return;
}
final GridRow control = (GridRow) getSkinnable();
final SpreadsheetGridView gridView = (SpreadsheetGridView) handle.getGridView();
final Grid grid = spreadsheetView.getGrid();
final int index = control.getIndex();
/**
* If this row is out of bounds, this means that the row is displayed
* either at the top or at the bottom. In any case, this row is not
* meant to be seen so we clear its children list in order not to show
* previous TableCell that could be there.
*/
if (index < 0 || index >= gridView.getItems().size()) {
getChildren().clear();
putCellsInCache();
return;
}
final List row = grid.getRows().get(index);
final List columns = spreadsheetView.getColumns();
final ObservableList, ?>> tableViewColumns = gridView.getColumns();
/**
* If we use "setGrid" on SpreadsheetView, we must be careful because we
* set our columns after (due to threading safety). So if, by mistake,
* we are in layout and the columns are set in SpreadsheetView, but not
* in TableView (yet). Then just return and wait for next calling.
*/
if (columns.size() != tableViewColumns.size()) {
return;
}
getSkinnable().setVisible(true);
// layout the individual column cells
double width;
double height;
final double verticalPadding = snappedTopInset() + snappedBottomInset();
final double horizontalPadding = snappedLeftInset()
+ snappedRightInset();
/**
* Here we make the distinction between the official controlHeight and
* the customHeight that we may apply.
*/
double controlHeight = getTableRowHeight(index);
double customHeight = controlHeight == Grid.AUTOFIT ? GridViewSkin.DEFAULT_CELL_HEIGHT : controlHeight;
final GridViewSkin skin = handle.getCellsViewSkin();
skin.hBarValue.set(index, true);
// determine the width of the visible portion of the table
double headerWidth = gridView.getWidth();
final double hbarValue = skin.getHBar().getValue();
/**
* FOR FIXED ROWS
*/
((GridRow) getSkinnable()).verticalShift.setValue(getFixedRowShift(index));
double fixedColumnWidth = 0;
List fixedCells = new ArrayList();
//We compute the cells here
putCellsInCache();
boolean firstVisibleCell = false;
for (int indexColumn = 0; indexColumn < columns.size(); indexColumn++) {
width = snapSize(columns.get(indexColumn).getWidth()) - snapSize(horizontalPadding);
final SpreadsheetCell spreadsheetCell = row.get(indexColumn);
boolean isVisible = !isInvisible(x, width, hbarValue, headerWidth, spreadsheetCell.getColumnSpan());
if (columns.get(indexColumn).isFixed()) {
isVisible = true;
}
if (!isVisible) {
if (firstVisibleCell) {
break;
}
x += width;
continue;
}
final CellView tableCell = getCell(gridView.getColumns().get(indexColumn));
cells.add(0, tableCell);
// In case the node was treated previously
tableCell.setManaged(true);
/**
* FOR FIXED COLUMNS
*/
double tableCellX = 0;
/**
* We need to update the fixedColumnWidth only on visible cell and
* we need to add the full width including the span.
*
* If we fail to do so, we may be in the situation where x will grow
* with the correct width and not fixedColumnWidth. Thus some cell
* that should be shifted will not because the computation based on
* fixedColumnWidth will be wrong.
*/
boolean increaseFixedWidth = false;
//Virtualization of column
// We translate that column by the Hbar Value if it's fixed
if (columns.get(indexColumn).isFixed()) {
if (hbarValue + fixedColumnWidth > x && spreadsheetCell.getColumn() == indexColumn) {
increaseFixedWidth = true;
tableCellX = Math.abs(hbarValue - x + fixedColumnWidth);
// tableCell.toFront();
fixedColumnWidth += width;
// isVisible = true; // If in fixedColumn, it's obviously visible
fixedCells.add(tableCell);
}
}
if (isVisible) {
final SpreadsheetView.SpanType spanType = grid.getSpanType(spreadsheetView, index, indexColumn);
switch (spanType) {
case ROW_SPAN_INVISIBLE:
case BOTH_INVISIBLE:
fixedCells.remove(tableCell);
getChildren().remove(tableCell);
// cells.remove(tableCell);
x += width;
continue; // we don't want to fall through
case COLUMN_SPAN_INVISIBLE:
fixedCells.remove(tableCell);
getChildren().remove(tableCell);
// cells.remove(tableCell);
continue; // we don't want to fall through
case ROW_VISIBLE:
final TableViewSpanSelectionModel sm = (TableViewSpanSelectionModel) handle.getGridView().getSelectionModel();
final TableColumn, ?> col = tableViewColumns.get(indexColumn);
/**
* In case this cell was selected before but we scroll
* up/down and it's invisible now. It has to pass his
* "selected property" to the new Cell in charge of
* spanning
*/
final TablePosition, ?> selectedPosition = sm.isSelectedRange(index, col, indexColumn);
// If the selected cell is in the same row, no need to re-select it
if (selectedPosition != null
//When shift selecting, all cells become ROW_VISIBLE so
//We avoid loop selecting here
&& skin.containsRow(index)
&& selectedPosition.getRow() != index) {
sm.clearSelection(selectedPosition.getRow(),
selectedPosition.getTableColumn());
sm.select(index, col);
}
case NORMAL_CELL: // fall through and carry on
if (tableCell.getIndex() != index) {
tableCell.updateIndex(index);
} else {
tableCell.updateItem(spreadsheetCell, false);
}
/**
* Here we need to add the cells on the first position
* because this row may contain some deported cells from
* other rows in order to be on top in term of z-order.
* So the cell we're currently adding must not recover
* them.
*/
if (tableCell.getParent() == null) {
getChildren().add(0, tableCell);
}
}
if (spreadsheetCell.getColumnSpan() > 1) {
/**
* we need to span multiple columns, so we sum up the width
* of the additional columns, adding it to the width
* variable
*/
for (int i = 1, colSpan = spreadsheetCell.getColumnSpan(), max1 = columns
.size() - indexColumn; i < colSpan && i < max1; i++) {
double tempWidth = snapSize(columns.get(indexColumn + i).getWidth());
width += tempWidth;
if (increaseFixedWidth) {
fixedColumnWidth += tempWidth;
}
}
}
/**
* If we are in autofit and the prefHeight of this cell is
* superior to the default cell height. Then we will use this
* new height for row's height.
*
* We then need to apply the value to previous cell, and also
* layout the children because since we are layouting upward,
* next rows needs to know that this row is bigger than usual.
*/
if (controlHeight == Grid.AUTOFIT && !tableCell.isEditing()) {
double tempHeight = tableCell.prefHeight(width);
if (tempHeight > customHeight) {
skin.rowHeightMap.put(index, tempHeight);
for (CellView cell : cells) {
cell.resize(cell.getWidth(), tempHeight);//cell.getHeight() + (tempHeight - GridViewSkin.DEFAULT_CELL_HEIGHT));
}
customHeight = tempHeight;
skin.getFlow().layoutChildren();
}
}
height = customHeight;
height = snapSize(height) - snapSize(verticalPadding);
/**
* We need to span multiple rows, so we sum up the height of all
* the rows. The height of the current row is ignored and the
* whole value is computed.
*/
if (spreadsheetCell.getRowSpan() > 1) {
height = 0;
final int maxRow = spreadsheetCell.getRow() + spreadsheetCell.getRowSpan();
for (int i = spreadsheetCell.getRow(); i < maxRow; ++i) {
height += snapSize(skin.getRowHeight(i));
}
}
tableCell.resize(width, height);
// We want to place the layout always at the starting cell.
double spaceBetweenTopAndMe = 0;
for (int p = spreadsheetCell.getRow(); p < index; ++p) {
spaceBetweenTopAndMe += skin.getRowHeight(p);
}
tableCell.relocate(x + tableCellX, snappedTopInset()
- spaceBetweenTopAndMe + ((GridRow) getSkinnable()).verticalShift.get());
// Request layout is here as (partial) fix for RT-28684
// tableCell.requestLayout();
} else {
getChildren().remove(tableCell);
}
x += width;
}
skin.fixedColumnWidth = fixedColumnWidth;
handleFixedCell(fixedCells, index);
removeUselessCell();
if(handle.getCellsViewSkin().lastRowLayout.get() == true){
handle.getCellsViewSkin().lastRowLayout.setValue(false);
}
}
/**
* Here we want to remove of the sceneGraph cells that are not used.
*/
private void removeUselessCell() {
Collection tempCells = getCellsMap().values();
getChildren().removeAll(tempCells);
}
/**
* This handles the fixed cells in column.
*
* @param fixedCells
* @param index
*/
private void handleFixedCell(List fixedCells, int index) {
/**
* If we have a fixedCell (in column) and that cell may be recovered by
* a rowSpan, we want to put that tableCell ahead in term of z-order. So
* we need to put it in another row.
*/
if (handle.getCellsViewSkin().rowToLayout.get(index)) {
GridRow gridRow = handle.getCellsViewSkin().getFlow().getTopRow();
if (gridRow != null) {
for (CellView cell : fixedCells) {
final double originalLayoutY = getSkinnable().getLayoutY() + cell.getLayoutY();
gridRow.removeCell(cell);
gridRow.addCell(cell);
if (handle.getCellsViewSkin().deportedCells.containsKey(gridRow)) {
handle.getCellsViewSkin().deportedCells.get(gridRow).add(cell);
} else {
Set temp = new HashSet<>();
temp.add(cell);
handle.getCellsViewSkin().deportedCells.put(gridRow, temp);
}
/**
* I need to have the layoutY of the original row, but also
* to remove the layoutY of the row I'm adding in. Because
* if the first row is fixed and is undergoing a bit of
* translate in order to be visible, we need to remove that
* "bit of translate".
*/
cell.relocate(cell.getLayoutX(), originalLayoutY - gridRow.getLayoutY());
}
}
} else {
for (CellView cell : fixedCells) {
cell.toFront();
}
}
}
/**
* Return the Cache. Here we use a WeakReference because the WeakHashMap is
* not working. TableCell added to it are not removed if the GC wants them.
* So we put the whole cache in WeakReference. In normal condition, the
* cache is not trashed that much and is efficient. In the case where the
* user scroll horizontally a lot, that cache can then be trashed in order
* to avoid OutOfMemoryError.
*
* @return
*/
private HashMap getCellsMap() {
if (cellsMap == null || cellsMap.get() == null) {
HashMap map = new HashMap<>();
cellsMap = new WeakReference<>(map);
return map;
}
return cellsMap.get();
}
/**
* This will put all current displayed cell into the cache.
*/
private void putCellsInCache() {
for (CellView cell : cells) {
getCellsMap().put(cell.getTableColumn(), cell);
}
cells.clear();
}
/**
* This will retrieve a cell for the specified column. If the cell exists in
* the cache, it's extracted from it. Otherwise, a cell is created.
*
* @param tcb
* @return
*/
private CellView getCell(TableColumnBase tcb) {
TableColumn tableColumn = (TableColumn) tcb;
CellView cell;
if (getCellsMap().containsKey(tableColumn)) {
return getCellsMap().remove(tableColumn);
} else {
cell = (CellView) tableColumn.getCellFactory().call(tableColumn);
cell.updateTableColumn(tableColumn);
cell.updateTableView(tableColumn.getTableView());
cell.updateTableRow(getSkinnable());
}
return cell;
}
/**
* Return the space we need to shift that row if it's fixed. Also update the {@link GridViewSkin#getCurrentlyFixedRow()
* } .
*
* @param index
* @return
*/
private double getFixedRowShift(int index) {
double tableCellY = 0;
int positionY = spreadsheetView.getFixedRows().indexOf(index);
//FIXME Integrate if fixedCellSize is enabled
//Computing how much space we need to translate
//because each row has different space.
double space = 0;
for (int o = 0; o < positionY; ++o) {
space += handle.getCellsViewSkin().getRowHeight(spreadsheetView.getFixedRows().get(o));
}
//If true, this row is fixed
if (positionY != -1 && getSkinnable().getLocalToParentTransform().getTy() <= space) {
//This row is a bit hidden on top so we translate then for it to be fully visible
tableCellY = space - getSkinnable().getLocalToParentTransform().getTy();
handle.getCellsViewSkin().getCurrentlyFixedRow().add(index);
} else {
handle.getCellsViewSkin().getCurrentlyFixedRow().remove(index);
}
return tableCellY;
}
/**
* Return the height of a row.
*
* @param row
* @return
*/
private double getTableRowHeight(int row) {
Double rowHeightCache = handle.getCellsViewSkin().rowHeightMap.get(row);
return rowHeightCache == null ? handle.getView().getGrid().getRowHeight(row) : rowHeightCache;
}
/**
* Return true if the current cell is part of the sceneGraph.
*
* @param x beginning of the cell
* @param width total width of the cell
* @param hbarValue
* @param headerWidth width of the visible portion of the tableView
* @param columnSpan
* @return
*/
private boolean isInvisible(double x, double width, double hbarValue,
double headerWidth, int columnSpan) {
return (x + width < hbarValue && columnSpan == 1) || (x > hbarValue + headerWidth);
}
@Override
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
double prefWidth = 0.0;
final List extends TableColumnBase/**/> visibleLeafColumns = handle.getGridView().getVisibleLeafColumns();
for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) {
prefWidth += visibleLeafColumns.get(i).getWidth();
}
return prefWidth;
}
@Override
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
return getSkinnable().getPrefHeight();
}
@Override
protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
return getSkinnable().getPrefHeight();
}
@Override
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy