1. shemnon
  2. ControlsFX

Commits

Henri Biestro  committed 3c36bc5

Refactor/renamed implementation classes

  • Participants
  • Parent commits 0452ced
  • Branches default

Comments (0)

Files changed (21)

File src/main/java/impl/org/controlsfx/spreadsheet/CellView.java

View file
+/**
+ * Copyright (c) 2013, 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 javafx.application.Platform;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.ObservableList;
+import javafx.event.EventHandler;
+import javafx.scene.control.ContentDisplay;
+import javafx.scene.control.Control;
+import javafx.scene.control.Skin;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TablePositionBase;
+import javafx.scene.control.TableSelectionModel;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TableView.TableViewFocusModel;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
+
+import org.controlsfx.control.spreadsheet.Grid;
+import org.controlsfx.control.spreadsheet.SpreadsheetCell;
+import org.controlsfx.control.spreadsheet.SpreadsheetCellType;
+import org.controlsfx.control.spreadsheet.SpreadsheetView;
+
+/**
+ *
+ * The View cell that will be visible on screen.
+ * It holds the {@link SpreadsheetCell}.
+ */
+public class CellView extends TableCell<ObservableList<SpreadsheetCell>, SpreadsheetCell> {
+
+    /***************************************************************************
+     *                                                                         *
+     * Static Fields                                                           *
+     *                                                                         *
+     **************************************************************************/
+    private static final String ANCHOR_PROPERTY_KEY = "table.anchor";
+
+    static TablePositionBase<?> getAnchor(Control table, TablePositionBase<?> focusedCell) {
+        return hasAnchor(table) ?
+                (TablePositionBase<?>) table.getProperties().get(ANCHOR_PROPERTY_KEY) :
+                    focusedCell;
+    }
+    static boolean hasAnchor(Control table) {
+        return table.getProperties().get(ANCHOR_PROPERTY_KEY) != null;
+    }
+    
+    
+    /***************************************************************************
+     *                                                                         *
+     * Constructor                                                             *
+     *                                                                         *
+     **************************************************************************/
+    public CellView() {
+    	hoverProperty().addListener(new ChangeListener<Boolean>() {
+            @Override
+            public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
+                final int row = getIndex();
+//                final SpreadsheetView spv = ((SpreadsheetRow)getTableRow()).getSpreadsheetView();
+
+                if (getItem() == null) {
+                    getTableRow().requestLayout();
+                // We only need to re-route if the rowSpan is large because
+                // it's the only case where it's not handled correctly.
+                }else if(getItem().getRowSpan() >1){
+                	 // If we are not at the top of the Spanned Cell
+                	if (t1 && row != getItem().getRow()) {
+                        hoverGridCell(getItem());
+                    } else if (!t1 && row != getItem().getRow()) {
+                        unHoverGridCell();
+                    }
+                }
+            }
+        });
+        //When we detect a drag, we start the Full Drag so that other event will be fired
+        this.addEventHandler(MouseEvent.DRAG_DETECTED,new EventHandler<MouseEvent>() {
+            @Override
+            public void handle(MouseEvent arg0) {
+                startFullDrag();
+            }});
+
+
+        setOnMouseDragEntered(new EventHandler<MouseEvent>(){
+            @Override
+            public void handle(MouseEvent arg0) {
+                dragSelect(arg0);
+            }});
+
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Public Methods                                                          *
+     *                                                                         *
+     **************************************************************************/
+    @Override
+    public void startEdit() {
+        if(!isEditable()) {
+            return;
+        }
+
+        final int column = this.getTableView().getColumns().indexOf(this.getTableColumn());
+        final int row = getIndex();
+        //We start to edit only if the Cell is a normal Cell (aka visible).
+        final SpreadsheetView spv = getSpreadsheetView();
+        final GridViewSkin sps = spv.getCellsViewSkin();
+        Grid grid = spv.getGrid();
+        final SpreadsheetView.SpanType type = grid.getSpanType(spv, row, column);
+        if ( type == SpreadsheetView.SpanType.NORMAL_CELL || type == SpreadsheetView.SpanType.ROW_VISIBLE) {
+        	
+        	/* FIXME Currently we're adding a row two times if it's located in the fixedRows.
+        	 * So we have a problem because some events, especially when editing
+        	 * are received in double.
+        	 * I don't know right now why this is happening but I have found a work-around.
+        	 * I check if the current SpreadsheetRow is referenced in the SpreadsheetView,
+        	 * if not, then I know I can throw it away (setManaged(false) ?)
+        	 */
+        	if(spv.getFixedRows().contains(row)){//row <= spv.getFixedRows().size()){
+	        	boolean flag = false;
+	        	for (int j = 0; j< sps.getCellsSize();j++ ) {
+	                    if(sps.getRow(j) == getTableRow()){
+	                    	flag = true;
+	                    }
+	            }
+	        	if(!flag){
+	        		getTableRow().setManaged(false);
+	        		return;
+	        	}
+        	}
+        	
+        	GridCellEditor editor = getEditor(getItem(), spv);
+            if(editor != null){
+                super.startEdit();
+                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
+                editor.startEdit();
+            }
+        }
+    }
+    
+    /**
+     * Return an instance of Editor specific to the Cell type
+     * We are not using the build-in editor-Cell because we cannot know in advance
+     * which editor we will need. Furthermore, we want to control the behavior very closely
+     * in regards of the spanned cell (invisible etc).
+     * @param cell The SpreadsheetCell
+     * @param bc The SpreadsheetCell
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    private GridCellEditor getEditor(final SpreadsheetCell cell, final SpreadsheetView spv) {
+    	SpreadsheetCellType<?> cellType = cell.getCellType();
+    	GridCellEditor editor = spv.getCellsViewSkin().getSpreadsheetCellEditorImpl();
+        if (editor.isEditing()){
+            return null;
+        } else {
+        	editor.updateSpreadsheetView(spv);
+        	editor.updateSpreadsheetCell(this);
+        	editor.updateDataCell(cell);
+        	editor.updateSpreadsheetCellEditor(cellType.getEditor(spv));
+            return editor;
+        }
+    }
+    
+    
+
+    @Override
+    public void commitEdit(SpreadsheetCell newValue) {
+        if (! isEditing()) {
+            return;
+        }
+        super.commitEdit(newValue);
+
+        setContentDisplay(ContentDisplay.TEXT_ONLY);
+
+        updateItem(newValue, false);
+
+    }
+
+    @Override
+    public void cancelEdit() {
+        if (! isEditing()) {
+            return;
+        }
+
+        super.cancelEdit();
+
+        setContentDisplay(ContentDisplay.TEXT_ONLY);
+        final Runnable r = new Runnable() {
+            @Override
+            public void run() {
+                getTableView().requestFocus();
+            }
+        };
+        Platform.runLater(r);
+    }
+
+    @Override
+    public void updateItem(final SpreadsheetCell item, boolean empty) {
+        final boolean emptyRow = getTableView().getItems().size() < getIndex() + 1;
+
+        /**
+         * don't call super.updateItem() because it will trigger cancelEdit() if
+         * the cell is being edited. It causes calling commitEdit() ALWAYS call
+         * cancelEdit as well which is undesired.
+         *
+         */
+        if (!isEditing()) {
+            super.updateItem(item, empty && emptyRow);
+        }
+        if (empty && isSelected()) {
+            updateSelected(false);
+        }
+        if (empty && emptyRow) {
+            setText(null);
+            //do not nullify graphic here. Let the TableRow to control cell dislay
+            //setGraphic(null);
+            setContentDisplay(null);
+        } else if (!isEditing() && item != null) {
+            show(item);
+            //Sometimes the hoverProperty is not called on exit. So the cell is affected to a new Item but
+            // the hover is still activated. So we fix it now.
+            if(isHover()){
+            	setHoverPublic(false);
+            }
+
+        }
+    }
+    
+    /**
+     * Set this SpreadsheetCell hoverProperty
+     *
+     * @param hover
+     */
+    void setHoverPublic(boolean hover) {
+        this.setHover(hover);
+        // We need to tell the SpreadsheetRow where this SpreadsheetCell is in to be in Hover
+        //Otherwise it's will not be visible
+        ((GridRow) this.getTableRow()).setHoverPublic(hover);
+    }
+
+    @Override
+    public String toString(){
+        return getItem().getRow()+"/"+getItem().getColumn();
+
+    }
+    /**
+     * Called in the gridRowSkinBase when doing layout
+     * This allow not to override opacity in the row and let the
+     * cell handle itself
+     */
+    public void show(final SpreadsheetCell item){
+        //We reset the settings
+        setText(item.getText());
+        setEditable(item.isEditable());
+        
+        getStyleClass().clear();
+        getStyleClass().add("spreadsheet-cell");
+        
+        // TODO bind
+        getStyleClass().addAll(item.getStyleClass());
+        
+        // Style
+//        final ObservableList<String> css = getStyleClass();
+//        if (css.size() == 1) {
+//            css.set(0, item.getStyleCss());
+//        }else{
+//            css.clear();
+//            css.add(item.getStyleCss());
+//        }
+    }
+
+    public void show(){
+        if (getItem() != null){
+            show(getItem());
+        }
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Protected Methods                                                          *
+     *                                                                         *
+     **************************************************************************/
+    @Override
+    protected Skin<?> createDefaultSkin() {
+        return GridViewSkin.createCellSkin(this);
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Private Methods                                                          *
+     *                                                                         *
+     **************************************************************************/
+    
+    private SpreadsheetView getSpreadsheetView() {
+        return ((GridRow)getTableRow()).getSpreadsheetView();
+    }
+    
+    /**
+     * A SpreadsheetCell is being hovered and we need to re-route the signal.
+     *
+     * @param cell The DataCell needed to be hovered.
+     * @param hover
+     */
+    private void hoverGridCell(SpreadsheetCell cell) {
+        CellView gridCell;
+        
+        final SpreadsheetView spv = getSpreadsheetView();
+        final GridViewSkin sps = spv.getCellsViewSkin();
+        final GridRow row = sps.getRow(spv.getFixedRows().size());
+        
+        if (sps.getCellsSize() !=0  && row.getIndex() <= cell.getRow()) {
+        	final GridRow rightRow = sps.getRow(spv.getFixedRows().size()+cell.getRow() - row.getIndex());
+            // We want to get the top of the spanned cell, so we need
+            // to access the fixedRows.size plus the difference between where we want to go and the first visibleRow (header excluded)
+            if( rightRow != null) {// Sometime when scrolling fast it's null so..
+                gridCell = rightRow.getGridCell(cell.getColumn());
+            } else {
+                gridCell = row.getGridCell(cell.getColumn());
+            }
+        } else { // If it's not, then it's the firstkey
+            gridCell = row.getGridCell(cell.getColumn());
+        }
+        
+        if(gridCell != null){
+	        gridCell.setHoverPublic(true);
+	        GridCellEditor editor = sps.getSpreadsheetCellEditorImpl();
+	        editor.setLastHover(gridCell);
+        }
+    }
+
+    /**
+     * Set Hover to false to the previous Cell we force to be hovered
+     */
+    private void unHoverGridCell() {
+        //If the top of the spanned cell is visible, then no problem
+        final SpreadsheetView spv = getSpreadsheetView();
+        final GridViewSkin sps = spv.getCellsViewSkin();
+        GridCellEditor editor = sps.getSpreadsheetCellEditorImpl();
+        CellView lastHover = editor.getLastHover();
+        if(editor.getLastHover() != null){
+        	lastHover.setHoverPublic(false);
+        }
+    }
+    
+    
+    /**
+     * Method that will select all the cells between the drag place and that cell.
+     * @param e
+     */
+    private void dragSelect(MouseEvent e) {
+
+        // If the mouse event is not contained within this tableCell, then
+        // we don't want to react to it.
+        if (! this.contains(e.getX(), e.getY())) {
+            return;
+        }
+
+        final TableView<ObservableList<SpreadsheetCell>> tableView = getTableView();
+        if (tableView == null) {
+            return;
+        }
+
+        final int count = tableView.getItems().size();
+        if (getIndex() >= count) {
+            return;
+        }
+
+        final TableSelectionModel<ObservableList<SpreadsheetCell>> sm = tableView.getSelectionModel();
+        if (sm == null) {
+            return;
+        }
+
+        final int row = getIndex();
+        final int column = tableView.getVisibleLeafIndex(getTableColumn());
+        // For spanned Cells
+        final SpreadsheetCell cell = (SpreadsheetCell) getItem();
+        final int rowCell = cell.getRow()+cell.getRowSpan()-1;
+        final int columnCell = cell.getColumn()+cell.getColumnSpan()-1;
+
+        final TableViewFocusModel<?> fm = tableView.getFocusModel();
+        if (fm == null) {
+            return;
+        }
+
+        final TablePositionBase<?> focusedCell = fm.getFocusedCell();
+        final MouseButton button = e.getButton();
+        if (button == MouseButton.PRIMARY) {
+            // we add all cells/rows between the current selection focus and
+            // this cell/row (inclusive) to the current selection.
+            final TablePositionBase<?> anchor = getAnchor(tableView, focusedCell);
+
+            // and then determine all row and columns which must be selected
+            int minRow = Math.min(anchor.getRow(), row);
+            minRow = Math.min(minRow, rowCell);
+            int maxRow = Math.max(anchor.getRow(), row);
+            maxRow = Math.max(maxRow, rowCell);
+            int minColumn = Math.min(anchor.getColumn(), column);
+            minColumn = Math.min(minColumn, columnCell);
+            int maxColumn = Math.max(anchor.getColumn(), column);
+            maxColumn = Math.max(maxColumn, columnCell);
+
+            // clear selection, but maintain the anchor
+            sm.clearSelection();
+
+            // and then perform the selection
+            for (int _row = minRow; _row <= maxRow; _row++) {
+                for (int _col = minColumn; _col <= maxColumn; _col++) {
+                    sm.select(_row, tableView.getVisibleLeafColumn(_col));
+                }
+            }
+        }
+
+    }
+}

File src/main/java/impl/org/controlsfx/spreadsheet/GridCellEditor.java

View file
+package impl.org.controlsfx.spreadsheet;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+
+import org.controlsfx.control.spreadsheet.SpreadsheetCell;
+import org.controlsfx.control.spreadsheet.SpreadsheetCellEditor;
+import org.controlsfx.control.spreadsheet.SpreadsheetView;
+
+public class GridCellEditor {
+
+	/***************************************************************************
+	 * * Protected/Private Fields * *
+	 **************************************************************************/
+
+	// transient properties - these fields will change based on the current
+	// cell being edited.
+	private SpreadsheetCell modelCell;
+	private CellView viewCell;
+
+	// private internal fields
+	private SpreadsheetEditor spreadsheetEditor;
+	private InvalidationListener editorListener;
+	private InvalidationListener il;
+	private boolean editing = false;
+	private SpreadsheetView spreadsheetView;
+	private SpreadsheetCellEditor<?> spreadsheetCellEditor;
+    private CellView lastHover = null;
+
+	/***************************************************************************
+	 * * Constructor * *
+	 **************************************************************************/
+
+	/**
+	 * Construct the SpreadsheetCellEditor.
+	 */
+	public GridCellEditor() {
+		this.spreadsheetEditor = new SpreadsheetEditor();
+	}
+
+	/***************************************************************************
+	 * * Public Methods * *
+	 **************************************************************************/
+	/**
+	 * Update the internal {@link SpreadsheetCell}.
+	 * @param cell
+	 */
+	public void updateDataCell(SpreadsheetCell cell) {
+		this.modelCell = cell;
+	}
+
+	/**
+	 * Update the internal {@link CellView}
+	 * @param cell
+	 */
+	public void updateSpreadsheetCell(CellView cell) {
+		this.viewCell = cell;
+	}
+
+	/**
+	 * Update the SpreadsheetView
+	 * @param spreadsheet
+	 */
+	public void updateSpreadsheetView(SpreadsheetView spreadsheet) {
+		this.spreadsheetView = spreadsheet;
+	}
+	/**
+	 * Update the SpreadsheetCellEditor
+	 * @param spreadsheetCellEditor2
+	 */
+	public void updateSpreadsheetCellEditor(final SpreadsheetCellEditor<?> spreadsheetCellEditor2) {
+		this.spreadsheetCellEditor = spreadsheetCellEditor2;
+	}
+    
+    public CellView getLastHover() {
+    	return lastHover;
+    }
+    
+    public void setLastHover(CellView lastHover) {
+    	this.lastHover = lastHover;
+    }
+    
+	/**
+	 * Whenever you want to stop the edition, you call that method.<br/>
+	 * True means you're trying to commit the value, then {@link #validateEdit()}
+	 * will be called in order to verify that the value is correct.<br/>
+	 * False means you're trying to cancel the value and it will be follow by {@link #end()}.<br/>
+	 * See SpreadsheetCellEditor description
+	 * @param b true means commit, false means cancel
+	 */
+	public void endEdit(boolean b){
+		if(b){
+			Object value = spreadsheetCellEditor.validateEdit();
+			if(value != null && viewCell != null){
+				modelCell.setItem(value);
+				viewCell.commitEdit(modelCell);
+				end();
+				spreadsheetCellEditor.end();
+			}
+		}else{
+			viewCell.cancelEdit();
+			end();
+			spreadsheetCellEditor.end();
+		}
+	}
+
+
+	/**
+	 * Return if this editor is currently being used.
+	 * @return if this editor is being used.
+	 */
+	public boolean isEditing() {
+		return editing;
+	}
+
+	public SpreadsheetCell getModelCell() {
+		return modelCell;
+	}
+
+	/***************************************************************************
+	 * * Protected/Private Methods * *
+	 **************************************************************************/
+	void startEdit() {
+		editing = true;
+		spreadsheetEditor.startEdit();
+
+		// If the SpreadsheetCell is deselected, we commit.
+		// Sometimes, when you you touch the scrollBar when editing,
+		// this is called way
+		// too late and the SpreadsheetCell is null, so we need to be
+		// careful.
+		il = new InvalidationListener() {
+			@Override
+			public void invalidated(Observable observable) {
+				endEdit(false);
+			}
+		};
+
+		viewCell.selectedProperty().addListener(il);
+
+		// In ANY case, we stop when something move in scrollBar Vertical
+		editorListener = new InvalidationListener() {
+			@Override
+			public void invalidated(Observable arg0) {
+				endEdit(false);
+			}
+		};
+		spreadsheetView.getCellsViewSkin().getVBar().valueProperty().addListener(editorListener);
+		//FIXME We need to REALLY find a way to stop edition when anything happen
+		// This is one way but it will need further investigation
+		spreadsheetView.disabledProperty().addListener(editorListener);
+
+		//Then we call the user editor in order for it to be ready
+		spreadsheetCellEditor.startEdit();
+
+		viewCell.setGraphic(spreadsheetCellEditor.getEditor());
+	}
+
+
+	private void end() {
+		editing = false;
+		spreadsheetEditor.end();
+		if (viewCell != null) {
+			viewCell.selectedProperty().removeListener(il);
+		}
+		il = null;
+
+		spreadsheetView.getCellsViewSkin().getVBar().valueProperty().removeListener(editorListener);
+		spreadsheetView.disabledProperty().removeListener(editorListener);
+		editorListener = null;
+		this.modelCell = null;
+		this.viewCell = null;
+	}
+
+
+	private class SpreadsheetEditor {
+
+		/***********************************************************************
+		 * * Private Fields * *
+		 **********************************************************************/
+		private GridRow original;
+		private boolean isMoved;
+
+		private int getCellCount() {
+			return spreadsheetView.getCellsViewSkin().getCellsSize();
+		}
+
+		private boolean addCell(CellView cell){
+			GridRow temp = spreadsheetView.getCellsViewSkin().getRow(getCellCount()-1-spreadsheetView.getFixedRows().size());
+			if(temp != null){
+				temp.addCell(cell);
+				return true;
+			}
+			return false;
+		}
+		/***********************************************************************
+		 * * Public Methods * *
+		 **********************************************************************/
+
+		/**
+		 * In case the cell is spanning in rows. We want the cell to be fully
+		 * accessible so we need to remove it from its tableRow and add it to the
+		 * last row possible. Then we translate the cell so that it's invisible for
+		 * the user.
+		 */
+		public void startEdit() {
+			// Case when RowSpan if larger and we're not on the last row
+			if (modelCell != null && modelCell.getRowSpan() > 1
+					&& modelCell.getRow() != getCellCount() - 1) {
+				original = (GridRow) viewCell.getTableRow();
+
+				final double temp = viewCell.getLocalToSceneTransform().getTy();
+				isMoved = addCell(viewCell);
+				if (isMoved) {
+					viewCell.setTranslateY(temp
+							- viewCell.getLocalToSceneTransform().getTy());
+					original.putFixedColumnToBack();
+				}
+			}
+		}
+
+		/**
+		 * When we have finish editing. We put the cell back to its right TableRow.
+		 */
+		public void end() {
+			if (modelCell != null && modelCell.getRowSpan() > 1) {
+				viewCell.setTranslateY(0);
+				if (isMoved) {
+					original.addCell(viewCell);
+					original.putFixedColumnToBack();
+				}
+			}
+		}
+	}
+}

File src/main/java/impl/org/controlsfx/spreadsheet/GridRow.java

View file
+/**
+ * Copyright (c) 2013, 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.Comparator;
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import javafx.scene.Node;
+import javafx.scene.control.Skin;
+import javafx.scene.control.TableRow;
+
+import org.controlsfx.control.spreadsheet.SpreadsheetCell;
+import org.controlsfx.control.spreadsheet.SpreadsheetView;
+
+
+/**
+ *
+ * The tableRow which will holds the SpreadsheetCell.
+ */
+public class GridRow extends TableRow<ObservableList<SpreadsheetCell>> {
+
+    /***************************************************************************
+     *                                                                         *
+     * Private Fields                                                          *
+     *                                                                         *
+     **************************************************************************/
+    private final SpreadsheetView spreadsheetView;
+    /**
+     * This is the index used by the VirtualFlow
+     * So the row can be with indexVirtualFlow at 32
+     * But if it is situated in the header, his index will be 0 (or the row in the header)
+     */
+    private Integer indexVirtualFlow = null;
+    private boolean layoutFixedColumns = false;
+    private Boolean currentlyFixed = false;
+
+    /***************************************************************************
+     *                                                                         *
+     * Constructor                                                             *
+     *                                                                         *
+     **************************************************************************/
+    public GridRow(SpreadsheetView spreadsheetView) {
+        super();
+        this.spreadsheetView = spreadsheetView;
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Public Methods                                                          *
+     *                                                                         *
+     **************************************************************************/
+
+    public int getIndexVirtualFlow(){
+        return indexVirtualFlow == null?getIndex():indexVirtualFlow;
+    }
+    
+    public void setIndexVirtualFlow(int i){
+        indexVirtualFlow = i;
+    }
+
+    public Boolean getCurrentlyFixed() {
+		return currentlyFixed;
+	}
+
+	/**
+	 * Indicate that this row is bonded on the top.
+	 * @param currentlyFixed
+	 */
+	public void setCurrentlyFixed(Boolean currentlyFixed) {
+		this.currentlyFixed = currentlyFixed;
+	}
+	
+    /**
+     * For the fixed columns in order to just re-layout the fixed columns
+     * @param b
+     */
+    public void setLayoutFixedColumns(boolean b){
+        layoutFixedColumns = b;
+    }
+
+    public boolean getLayoutFixedColumns(){
+        return layoutFixedColumns;
+    }
+
+    /**
+     * When unfixing some Columns, we need to put the previously FixedColumns back
+     * if we want the hover to be dealt correctly
+     * @param size
+     */
+    public void putFixedColumnToBack() {
+        final List<Node> tset = new ArrayList<>(getChildren());
+        tset.sort( new Comparator<Node>() {
+            @Override
+            public int compare(Node o1, Node o2) {
+                // In case it's null (some rows are initiated after rowCount)
+                if(((CellView)o1).getItem() == null || ((CellView)o2).getItem() == null){
+                    return -1;
+                }
+                final int lhs = getTableView().getColumns().indexOf(((CellView) o1).getTableColumn());
+                final int rhs = getTableView().getColumns().indexOf(((CellView) o2).getTableColumn());
+                if (lhs < rhs) {
+                    return -1;
+                }
+                if (lhs > rhs) {
+                    return +1;
+                }
+                return 0;
+
+            }
+        });
+        getChildren().setAll(tset);
+    }
+
+
+    public void addCell(CellView cell){
+        getChildren().add(cell);
+    }
+
+    public void removeCell(CellView gc) {
+        getChildren().remove(gc);
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Protected Methods                                                       *
+     *                                                                         *
+     **************************************************************************/
+
+    SpreadsheetView getSpreadsheetView() {
+        return spreadsheetView;
+    }
+
+    /**
+     * Set this SpreadsheetRow hoverProperty
+     * @param hover
+     */
+    void setHoverPublic(boolean hover) {
+        this.setHover(hover);
+    }
+
+    /**
+     * Return the SpreadsheetCell at the specified column.
+     * We have to be careful because if we have fixedColumns
+     * then the fixedColumns cells will be at the end of the Children's List
+     * @param col
+     * @return the corresponding SpreadsheetCell
+     */
+    CellView getGridCell(int col){
+    	
+    	// Too much complication for a minor effect, simple is better.
+ 	/*	final int max = getChildrenUnmodifiable().size()-1;
+ 		int j = max;
+ 		while(j>= 0 && ((SpreadsheetCellImpl<?>)getChildrenUnmodifiable().get(j)).getItem().getColumn() != max){
+ 			--j;
+ 		}
+ 		
+    	int fixedColSize = j == -1? 0:max -j;
+    	
+    	//If any cells was moved to the end
+        if(fixedColSize != 0){
+        	//if the requested column is fixed
+            if(spreadsheetView.getColumns().get(col).getCurrentlyFixed()){
+            	final int indexCol = spreadsheetView.getFixedColumns().indexOf(col);
+                return (SpreadsheetCellImpl<?>) getChildrenUnmodifiable().get(getChildrenUnmodifiable().size() + indexCol - fixedColSize);
+            } else {
+                return (SpreadsheetCellImpl<?>) getChildrenUnmodifiable().get( col- fixedColSize );
+            }
+        }else{
+            return (SpreadsheetCellImpl<?>) getChildrenUnmodifiable().get(col);
+        }*/
+    	
+        for(Node node:getChildrenUnmodifiable()){
+        	CellView cellView = (CellView) node;
+        	SpreadsheetCell cell = cellView.getItem();
+        	if(cell.getColumn() == col){
+        		return cellView;
+        	}
+        }
+        return null;
+    }
+
+    @Override
+    protected Skin<?> createDefaultSkin() {
+        return new GridRowSkin(this,spreadsheetView);
+    }
+
+}

File src/main/java/impl/org/controlsfx/spreadsheet/GridRowSkin.java

View file
+/**
+ * Copyright (c) 2013, 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 javafx.collections.ObservableList;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableColumnBase;
+import javafx.scene.control.TablePosition;
+import javafx.scene.control.TableRow;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TableView.TableViewSelectionModel;
+
+import org.controlsfx.control.spreadsheet.Grid;
+import org.controlsfx.control.spreadsheet.SpreadsheetCell;
+import org.controlsfx.control.spreadsheet.SpreadsheetView;
+
+import com.sun.javafx.scene.control.skin.TableRowSkin;
+
+public class GridRowSkin extends TableRowSkin<ObservableList<SpreadsheetCell>> {
+    
+    SpreadsheetView spreadsheetView;
+    private TableView<ObservableList<SpreadsheetCell>> tableView;
+    
+    public GridRowSkin(TableRow<ObservableList<SpreadsheetCell>> tableRow,
+            SpreadsheetView spreadsheetView) {
+        super(tableRow);
+        this.spreadsheetView = spreadsheetView;
+        this.tableView = (TableView<ObservableList<SpreadsheetCell>>) spreadsheetView.getCellsViewSkin().getNode();
+    }
+
+    /**
+     * We need to override this since B105 because it's messing up our
+     * fixedRows and also our rows CSS.. (kind of flicker)
+     */
+    @Override protected void handleControlPropertyChanged(String p) {
+        if ("ITEM".equals(p)) {
+            updateCells = true;
+            getSkinnable().requestLayout();
+        } else if ("INDEX".equals(p)){
+           /* // update the index of all children cells (RT-29849)
+            final int newIndex = getSkinnable().getIndex();
+            for (int i = 0, max = cells.size(); i < max; i++) {
+                cells.get(i).updateIndex(newIndex);
+            }*/
+        } else  {
+            super.handleControlPropertyChanged(p);
+        }
+    }
+    
+    @Override
+    protected void layoutChildren(double x, final double y, final double w,
+            final double h) {
+
+        /**
+         * RT-26743:TreeTableView: Vertical Line looks unfinished. We used to
+         * not do layout on cells whose row exceeded the number of items, but
+         * now we do so as to ensure we get vertical lines where expected in
+         * cases where the vertical height exceeds the number of items.
+         */
+        // I put that at the very beginning in the hope that I will not have
+        // that extra row at the bottom layouting.
+        final TableRow<ObservableList<SpreadsheetCell>> control = (TableRow<ObservableList<SpreadsheetCell>>) getSkinnable();
+        final int index = control.getIndex();
+        if (index < 0 || index >= tableView.getItems().size()) {
+            control.setOpacity(0);
+            return;
+        }
+        control.setOpacity(1);
+        checkState(true);
+        if (cellsMap.isEmpty()) { return; }
+
+        final ObservableList<? extends TableColumnBase<?, ?>> visibleLeafColumns = getVisibleLeafColumns();
+        if (visibleLeafColumns.isEmpty()) {
+            super.layoutChildren(x, y, w, h);
+            return;
+        }
+
+     
+        // determine the width of the visible portion of the table
+        double headerWidth =  tableView.getWidth();
+        
+        Grid grid = spreadsheetView.getGrid();
+        
+        // layout the individual column cells
+        double width;
+        double height;
+
+        final double verticalPadding = snappedTopInset() + snappedBottomInset();
+        final double horizontalPadding = snappedLeftInset()
+                + snappedRightInset();
+        final double controlHeight = control.getHeight();
+
+        
+        /**
+         * FOR FIXED ROWS
+         */
+        double tableCellY = 0;
+        int positionY = spreadsheetView.getFixedRows().indexOf(index);
+        //If true, this row is fixed
+        if (positionY != -1 && getSkinnable().getLocalToParentTransform().getTy() <= positionY * GridViewSkin.DEFAULT_CELL_HEIGHT) {
+        	//This row is a bit hidden on top so we translate then for it to be fully visible
+            tableCellY = positionY * GridViewSkin.DEFAULT_CELL_HEIGHT
+                    - getSkinnable().getLocalToParentTransform().getTy();
+            ((GridRow)getSkinnable()).setCurrentlyFixed(true);
+        }else{
+        	((GridRow)getSkinnable()).setCurrentlyFixed(false);
+        }
+
+        double fixedColumnWidth = 0;
+        for (int column = 0; column < cells.size(); column++) {
+
+            final CellView tableCell = (CellView) cells.get(column);
+
+            // In case the node was treated previously
+            tableCell.setOpacity(1);
+
+            width = snapSize(tableCell.prefWidth(-1))
+                    - snapSize(horizontalPadding);
+            height = Math.max(controlHeight, tableCell.prefHeight(-1));
+            height = snapSize(height) - snapSize(verticalPadding);
+
+            /**
+             * FOR FIXED COLUMNS
+             */
+            double tableCellX = 0;
+            final double hbarValue = spreadsheetView.getCellsViewSkin().getHBar().getValue();
+            
+            //Virtualization of column
+            final SpreadsheetCell cellSpan = grid.getRows().get(index).get(column);
+            boolean isVisible = !isInvisible(x,width,hbarValue,headerWidth,cellSpan.getColumnSpan());
+            
+            // We translate that column by the Hbar Value if it's fixed
+            if (spreadsheetView.getColumns().get(column).isFixed()) {
+                
+                 if(hbarValue + fixedColumnWidth >x){
+                	 tableCellX = Math.abs(hbarValue - x + fixedColumnWidth); 
+                	 tableCell.toFront();
+                	 fixedColumnWidth += tableCell.getWidth();
+                	 isVisible = true; // If in fixedColumn, it's obviously visible
+                 }
+            }
+
+            
+//            if (fixedCellSizeProperty().get() > 0) {
+                // we determine if the cell is visible, and if not we have the
+                // ability to take it out of the scenegraph to help improve
+                // performance. However, we only do this when there is a
+                // fixed cell length specified in the TableView. This is because
+                // when we have a fixed cell length it is possible to know with
+                // certainty the height of each TableCell - it is the fixed
+                // value
+                // provided by the developer, and this means that we do not have
+                // to concern ourselves with the possibility that the height
+                // may be variable and / or dynamic.
+//                isVisible = isColumnPartiallyOrFullyVisible(tableColumn);
+//            }
+
+            if (isVisible) {
+                if (/*fixedCellSizeProperty().get() > 0
+                        &&*/ tableCell.getParent() == null) {
+                    getChildren().add(0,tableCell);
+                }
+
+                final SpreadsheetView.SpanType spanType = grid.getSpanType(spreadsheetView, index, column);
+
+                switch (spanType) {
+                    case ROW_SPAN_INVISIBLE :
+                    case BOTH_INVISIBLE :
+                        tableCell.setOpacity(0);
+                        tableCell.resize(width, height);
+                        tableCell.relocate(x + tableCellX, snappedTopInset()
+                                + tableCellY);
+
+                        x += width;
+                        continue; // we don't want to fall through
+                    case COLUMN_SPAN_INVISIBLE :
+                        tableCell.setOpacity(0);
+                        tableCell.resize(width, height);
+                        tableCell.relocate(x + tableCellX, snappedTopInset()
+                                + tableCellY);
+                        continue; // we don't want to fall through
+                        // infact, we return to the loop here
+                    case ROW_VISIBLE :
+                        // To be sure that the text is the same
+                        // in case we modified the DataCell after that
+                        // SpreadsheetCell was created
+                        final TableViewSelectionModel<ObservableList<SpreadsheetCell>> sm = spreadsheetView.getSelectionModel();
+                        final TableColumn<ObservableList<SpreadsheetCell>, ?> col = tableView.getColumns().get(column);
+
+                        // 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<ObservableList<SpreadsheetCell>, ?> selectedPosition = isSelectedRange(index, col, column);
+                        // If the selected cell is in the same row, no need to re-select it
+                        if (selectedPosition != null
+                                && selectedPosition.getRow() != index) {
+                            sm.clearSelection(selectedPosition.getRow(),
+                                    selectedPosition.getTableColumn());
+                            sm.select(index, col);
+                        }
+                    case NORMAL_CELL : // fall through and carry on
+                        tableCell.show();
+                }
+
+                if (cellSpan != null) {
+                    if (cellSpan.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 = cellSpan.getColumnSpan(), max1 = cells
+                                .size() - column; i < colSpan && i < max1; i++) {
+                            // calculate the width
+//                            final Node adjacentNode = (Node) getChildren().get(
+//                                    column + i);
+                            width += snapSize(spreadsheetView.getColumns().get(column+i).getWidth());//adjacentNode.maxWidth(-1));
+                        }
+                    }
+
+                    if (cellSpan.getRowSpan() > 1) {
+                        // we need to span multiple rows, so we sum up
+                        // the height of the additional rows, adding it
+                        // to the height variable
+                        for (int i = 1; i < cellSpan.getRowSpan(); i++) {
+                            // calculate the height
+                            final double rowHeight = GridViewSkin.DEFAULT_CELL_HEIGHT;// getTableRowHeight(index
+                                                                       // + i,
+                                                                       // getSkinnable());
+                            height += snapSize(rowHeight);
+                        }
+                    }
+                }
+
+                tableCell.resize(width, height);
+                // We want to place the layout always at the starting cell.
+                final double spaceBetweenTopAndMe = (index - cellSpan.getRow())
+                        * GridViewSkin.DEFAULT_CELL_HEIGHT;
+                tableCell.relocate(x + tableCellX, snappedTopInset()
+                        - spaceBetweenTopAndMe + tableCellY);
+
+                // Request layout is here as (partial) fix for RT-28684
+                // tableCell.requestLayout();
+            } else {
+//                if (fixedCellSizeProperty().get() > 0) {
+                    // we only add/remove to the scenegraph if the fixed cell
+                    // length support is enabled - otherwise we keep all
+                    // TableCells in the scenegraph
+                    getChildren().remove(tableCell);
+//                }
+            }
+            
+            x += width;
+           
+        }
+    }
+
+    /**
+     * 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);
+	}
+    public TablePosition<ObservableList<SpreadsheetCell>, ?> isSelectedRange(int row, TableColumn<ObservableList<SpreadsheetCell>, ?> column, int col) {
+
+        final SpreadsheetCell cellSpan = tableView.getItems().get(row).get(col);
+        final int infRow = cellSpan.getRow();
+        final int supRow = infRow + cellSpan.getRowSpan();
+
+        final int infCol = cellSpan.getColumn();
+        final int supCol = infCol + cellSpan.getColumnSpan();
+        for (Object tp : spreadsheetView.getSelectionModel().getSelectedCells()) {
+        	 TablePosition<ObservableList<SpreadsheetCell>, ?> tp1 = ( TablePosition<ObservableList<SpreadsheetCell>, ?>)tp;
+            if (tp1.getRow() >= infRow && tp1.getRow() < supRow && tp1.getColumn() >= infCol && tp1.getColumn() < supCol) {
+                return tp1;
+            }
+        }
+        return null;
+    }
+}

File src/main/java/impl/org/controlsfx/spreadsheet/GridView.java

View file
+/**
+ * Copyright (c) 2013, 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 javafx.collections.ObservableList;
+import javafx.scene.control.TableView;
+
+import org.controlsfx.control.spreadsheet.SpreadsheetCell;
+import org.controlsfx.control.spreadsheet.SpreadsheetView;
+
+public class GridView extends TableView<ObservableList<SpreadsheetCell>> {
+	private final SpreadsheetView spreadsheet;
+
+	public GridView(SpreadsheetView spreadsheet) {
+		this.spreadsheet = spreadsheet;
+	}
+
+	@Override
+	protected String getUserAgentStylesheet() {
+		return SpreadsheetView.class.getResource("spreadsheet.css")
+				.toExternalForm();
+	}
+
+	@Override
+	protected javafx.scene.control.Skin<?> createDefaultSkin() {
+		return new GridViewSkin(spreadsheet);
+	}
+};

File src/main/java/impl/org/controlsfx/spreadsheet/GridViewSkin.java

View file
+/**
+ * Copyright (c) 2013, 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.lang.reflect.Field;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.property.BooleanProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import javafx.geometry.HPos;
+import javafx.geometry.VPos;
+import javafx.scene.control.Skin;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableColumnBase;
+import javafx.scene.control.TableFocusModel;
+import javafx.scene.control.TableRow;
+import javafx.scene.control.TableView;
+import javafx.util.Callback;
+
+import org.controlsfx.control.spreadsheet.SpreadsheetCell;
+import org.controlsfx.control.spreadsheet.SpreadsheetColumn;
+import org.controlsfx.control.spreadsheet.SpreadsheetView;
+
+import com.sun.javafx.scene.control.skin.TableCellSkin;
+import com.sun.javafx.scene.control.skin.TableHeaderRow;
+import com.sun.javafx.scene.control.skin.TableViewSkin;
+import com.sun.javafx.scene.control.skin.VirtualFlow;
+import com.sun.javafx.scene.control.skin.VirtualScrollBar;
+
+/**
+ * Despite the name, this skin is actually the skin of the TableView contained
+ * within the SpreadsheetView. The skin for the SpreadsheetView itself currently
+ * resides inside the SpreadsheetView constructor!
+ * 
+ */
+public class GridViewSkin extends TableViewSkin<ObservableList<SpreadsheetCell>> {
+    /** Default height of a cell. */
+
+    static final double DEFAULT_CELL_HEIGHT;
+    static {
+        double cell_size = 24.0;
+        try {
+            Class<?> clazz = com.sun.javafx.scene.control.skin.CellSkinBase.class;
+            Field f = clazz.getDeclaredField("DEFAULT_CELL_SIZE");
+            f.setAccessible(true);
+            cell_size = f.getDouble(null);
+        } catch (NoSuchFieldException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (SecurityException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IllegalArgumentException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        DEFAULT_CELL_HEIGHT = cell_size;
+    }
+    /** Default with of a "row header?". */
+    private final double DEFAULT_ROWHEADER_WIDTH = 50.0;
+    /** The editor. */
+    private GridCellEditor spreadsheetCellEditorImpl;
+
+    
+    private final double rowHeaderWidth = DEFAULT_ROWHEADER_WIDTH;
+    
+    //@Override
+    public static Skin<?> createCellSkin(CellView cell) {
+        return new TableCellSkin<>(cell);
+    }
+    
+    public double getRowHeaderWidth() {
+        return rowHeaderWidth;
+    }
+
+    protected SpreadsheetView spreadsheetView;
+    protected VerticalHeader rowHeader;
+
+    public GridViewSkin(final SpreadsheetView spreadsheetView) {
+        super(spreadsheetView.getCellsView());
+        this.spreadsheetView = spreadsheetView;
+        spreadsheetCellEditorImpl = new GridCellEditor();
+        TableView<ObservableList<SpreadsheetCell>> tableView = spreadsheetView.getCellsView();
+        tableView.setEditable(true);
+
+        // Do nothing basically but give access to the Hover Property.
+        tableView.setRowFactory(new Callback<TableView<ObservableList<SpreadsheetCell>>, TableRow<ObservableList<SpreadsheetCell>>>() {
+            @Override public TableRow<ObservableList<SpreadsheetCell>> call(TableView<ObservableList<SpreadsheetCell>> p) {
+                return new GridRow(spreadsheetView);
+            }
+        });
+
+        tableView.setFixedCellSize(getDefaultCellSize());
+
+        tableView.getStyleClass().add("cell-spreadsheet");
+
+        /*****************************************************************
+         * MODIFIED BY NELLARMONIA
+         *****************************************************************/
+        spreadsheetView.getFixedRows().addListener(fixedRowsListener);
+        spreadsheetView.getFixedColumns().addListener(fixedColumnsListener);
+//        spreadsheetView.setHbar(getFlow().getHorizontalBar());
+//        spreadsheetView.setVbar(getFlow().getVerticalBar());
+        /*****************************************************************
+         * END MODIFIED BY NELLARMONIA
+         *****************************************************************/
+        init();
+    }
+
+	protected void init() {
+        getFlow().getVerticalBar().valueProperty()
+                .addListener(vbarValueListener);
+        rowHeader = new VerticalHeader(this, spreadsheetView, rowHeaderWidth);
+        getChildren().addAll(rowHeader);
+
+        rowHeader.init(this);
+        ((HorizontalHeader) getTableHeaderRow()).init();
+        getFlow().init(spreadsheetView);
+    }
+
+    @Override
+    protected void layoutChildren(double x, double y, double w, final double h) {
+        if (spreadsheetView == null) { return; }
+        if (spreadsheetView.showRowHeaderProperty().get()) {
+            x += rowHeaderWidth;
+            w -= rowHeaderWidth;
+        }
+
+        super.layoutChildren(x, y, w, h);
+
+        final double baselineOffset = getSkinnable().getLayoutBounds()
+                .getHeight() / 2;
+        double tableHeaderRowHeight = 0;
+
+        if (spreadsheetView.showColumnHeaderProperty().get()) {
+            // position the table header
+            tableHeaderRowHeight = getTableHeaderRow().prefHeight(-1);
+            layoutInArea(getTableHeaderRow(), x, y, w, tableHeaderRowHeight,
+                    baselineOffset, HPos.CENTER, VPos.CENTER);
+
+            y += tableHeaderRowHeight;
+        } else {
+            // FIXME try to hide the columnHeader
+        	// FIXME tweak open in RT-32673
+        }
+
+        if (spreadsheetView.showRowHeaderProperty().get()) {
+            layoutInArea(rowHeader, x - rowHeaderWidth, y
+                    - tableHeaderRowHeight, w, h, baselineOffset, HPos.CENTER,
+                    VPos.CENTER);
+        }
+    }
+    final InvalidationListener vbarValueListener = new InvalidationListener() {
+        @Override
+        public void invalidated(Observable valueModel) {
+            verticalScroll();
+        }
+    };
+
+    protected void verticalScroll() {
+        rowHeader.updateScrollY();
+    }
+
+    @Override
+    protected void onFocusPreviousCell() {
+        final TableFocusModel<?, ?> fm = getFocusModel();
+        if (fm == null) { return; }
+        /*****************************************************************
+         * MODIFIED BY NELLARMONIA
+         *****************************************************************/
+        final int row = fm.getFocusedIndex();
+        // We try to make visible the rows that may be hiden by Fixed rows
+        if (!getFlow().getCells().isEmpty()
+                && getFlow().getCells().get(getFlow().getFixedRows().size())
+                        .getIndex() > row
+                && !getFlow().getFixedRows().contains(row)) {
+            flow.scrollTo(row);
+        } else {
+            flow.show(row);
+        }
+        scrollHorizontally();
+        /*****************************************************************
+         * END OF MODIFIED BY NELLARMONIA
+         *****************************************************************/
+    }
+
+    @Override
+    protected void onFocusNextCell() {
+        final TableFocusModel<?, ?> fm = getFocusModel();
+        if (fm == null) { return; }
+        /*****************************************************************
+         * MODIFIED BY NELLARMONIA
+         *****************************************************************/
+        final int row = fm.getFocusedIndex();
+        // We try to make visible the rows that may be hiden by Fixed rows
+        if (!getFlow().getCells().isEmpty()
+                && getFlow().getCells().get(getFlow().getFixedRows().size())
+                        .getIndex() > row
+                && !getFlow().getFixedRows().contains(row)) {
+            flow.scrollTo(row);
+        } else {
+            flow.show(row);
+        }
+        scrollHorizontally();
+        /*****************************************************************
+         * END OF MODIFIED BY NELLARMONIA
+         *****************************************************************/
+    }
+    @Override
+    protected void onSelectPreviousCell() {
+        super.onSelectPreviousCell();
+        scrollHorizontally();
+    }
+
+    @Override
+    protected void onSelectNextCell() {
+        super.onSelectNextCell();
+        scrollHorizontally();
+    }
+    
+    /**
+     * @return the defaultCellSize
+     */
+    public double getDefaultCellSize() {
+        return DEFAULT_CELL_HEIGHT;
+    }
+
+    /**
+     * We listen on the FixedRows in order to do the modification in the
+     * VirtualFlow
+     */
+    private final ListChangeListener<Integer> fixedRowsListener = new ListChangeListener<Integer>() {
+        @Override
+        public void onChanged(Change<? extends Integer> c) {
+           /* while (c.next()) {
+                for (final Integer remitem : c.getRemoved()) {
+                    getFlow().getFixedRows().remove(remitem);
+                }
+                for (final Integer additem : c.getAddedSubList()) {
+                    getFlow().getFixedRows().add(additem);
+                }
+            }*/
+            // requestLayout() not responding immediately..
+            getFlow().layoutTotal();
+        }
+
+    };
+
+    /**
+     * We listen on the FixedColumns in order to do the modification in the
+     * VirtualFlow
+     */
+    private final ListChangeListener<SpreadsheetColumn<?>> fixedColumnsListener = new ListChangeListener<SpreadsheetColumn<?>>() {
+        @Override
+        public void onChanged(Change<? extends SpreadsheetColumn<?>> c) {
+            if (spreadsheetView.getFixedColumns().size() > c.getList().size()) {
+                for (int i = 0; i < getFlow().getCells().size(); ++i) {
+                    ((GridRow) getFlow().getCells().get(i))
+                            .putFixedColumnToBack();
+                }
+            }
+
+            /*while (c.next()) {
+                for (final Integer remitem : c.getRemoved()) {
+                    getFlow().getFixedColumns().remove(remitem);
+                }
+                for (final Integer additem : c.getAddedSubList()) {
+                    getFlow().getFixedColumns().add(additem);
+                }
+            }*/
+            // requestLayout() not responding immediately..
+            getFlow().layoutTotal();
+        }
+
+    };
+
+    @Override
+    protected VirtualFlow<TableRow<ObservableList<SpreadsheetCell>>> createVirtualFlow() {
+        return new GridVirtualFlow<TableRow<ObservableList<SpreadsheetCell>>>();
+    }
+
+    protected TableHeaderRow createTableHeaderRow() {
+        return new HorizontalHeader(this);
+    }
+
+    BooleanProperty getTableMenuButtonVisibleProperty() {
+        return tableMenuButtonVisibleProperty();
+    }
+
+    @Override
+    protected void scrollHorizontally(TableColumn<ObservableList<SpreadsheetCell>, ?> col) {
+
+        if (col == null || !col.isVisible()) { return; }
+
+        // work out where this column header is, and it's width (start -> end)
+        double start = 0;// scrollX;
+        for (TableColumnBase<?, ?> c : getVisibleLeafColumns()) {
+            if (c.equals(col)) break;
+            start += c.getWidth();
+        }
+
+        /*****************************************************************
+         * MODIFIED BY NELLARMONIA We modifed this function so that we ensure
+         * that any selected cells will not be below a fixed column. Because
+         * when there's some fixed columns, the "left border" is not the table
+         * anymore, but the right side of the last fixed columns.
+         *****************************************************************/
+        // We add the fixed columns width
+        final double fixedColumnWidth = getFixedColumnWidth();
+
+        /*****************************************************************
+         * END OF MODIFIED BY NELLARMONIA
+         *****************************************************************/
+        final double end = start + col.getWidth();
+
+        // determine the visible width of the table
+        final double headerWidth = getSkinnable().getWidth()
+                - snappedLeftInset() - snappedRightInset();
+
+        // determine by how much we need to translate the table to ensure that
+        // the start position of this column lines up with the left edge of the
+        // tableview, and also that the columns don't become detached from the
+        // right edge of the table
+        final double pos = getFlow().getHorizontalBar().getValue();
+        final double max = getFlow().getHorizontalBar().getMax();
+        double newPos;
+
+        /*****************************************************************
+         * MODIFIED BY NELLARMONIA
+         *****************************************************************/
+        if (start < pos + fixedColumnWidth && start >= 0
+                && start >= fixedColumnWidth) {
+            newPos = start - fixedColumnWidth < 0 ? start : start
+                    - fixedColumnWidth;
+        } else {
+            final double delta = start < 0 || end > headerWidth ? start - pos
+                    - fixedColumnWidth : 0;
+            newPos = pos + delta > max ? max : pos + delta;
+        }
+
+        /*****************************************************************
+         * END OF MODIFIED BY NELLARMONIA
+         *****************************************************************/
+
+        // FIXME we should add API in VirtualFlow so we don't end up going
+        // direct to the hbar.
+        // actually shift the flow - this will result in the header moving
+        // as well
+        getFlow().getHorizontalBar().setValue(newPos);
+    }
+
+    /**
+     * Calc the width of the fixed columns in order not to select cells that are
+     * hidden by the fixed columns
+     * 
+     * @return
+     */
+    private double getFixedColumnWidth() {
+        double fixedColumnWidth = 0;
+        if (!spreadsheetView.getFixedColumns().isEmpty()) {
+            for (int i = 0, max = spreadsheetView.getFixedColumns().size(); i < max; ++i) {
+                final TableColumnBase<ObservableList<SpreadsheetCell>, ?> c = getVisibleLeafColumn(i);
+                fixedColumnWidth += c.getWidth();
+            }
+        }
+        return fixedColumnWidth;
+    }
+
+    public GridVirtualFlow<?> getFlow() {
+        return (GridVirtualFlow<?>) flow;
+    }
+
+    public GridRow getCell(int index) {
+        return (GridRow) getFlow().getCells().get(index);
+    }
+    
+    public int getCellsSize() {
+        return getFlow().getCells().size();
+    }
+    
+    public VirtualScrollBar getHBar() {
+        return getFlow().getHorizontalBar();
+    }
+    
+    public VirtualScrollBar getVBar() {
+        return getFlow().getVerticalBar();
+    }
+
+    public GridRow getRow(int index) {
+        return spreadsheetView.getCellsViewSkin().getCell(index);
+    }
+    
+    /**
+     * A list of Integer with the current selected Rows. This is useful for columnheader and
+     * RowHeader because they need to highligh when a selection is made.
+     */
+    private final ObservableList<Integer> selectedRows = FXCollections.observableArrayList();
+    public ObservableList<Integer> getSelectedRows() {
+        return selectedRows;
+    }
+
+    /**
+     * A list of Integer with the current selected Columns. This is useful for columnheader and
+     * RowHeader because they need to highligh when a selection is made.
+     */
+    private final ObservableList<Integer> selectedColumns= FXCollections.observableArrayList();
+    public ObservableList<Integer> getSelectedColumns() {
+        return selectedColumns;
+    }
+
+	public GridCellEditor getSpreadsheetCellEditorImpl() {
+		return spreadsheetCellEditorImpl;
+	}
+
+}

File src/main/java/impl/org/controlsfx/spreadsheet/GridVirtualFlow.java

View file
+/**
+ * Copyright (c) 2013, 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.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.ObservableList;
+import javafx.scene.control.Cell;
+import javafx.scene.control.IndexedCell;
+import javafx.scene.control.TableRow;
+
+import org.controlsfx.control.spreadsheet.SpreadsheetCell;
+import org.controlsfx.control.spreadsheet.SpreadsheetView;
+
+import com.sun.javafx.scene.control.skin.VirtualFlow;
+import com.sun.javafx.scene.control.skin.VirtualScrollBar;
+
+final class GridVirtualFlow<T extends IndexedCell<?>> extends VirtualFlow<T> {
+    
+    private static final Comparator<GridRow> ROWCMP = new Comparator<GridRow>() {
+        @Override
+        public int compare(GridRow o1, GridRow o2) {
+            final int lhs = o1.getIndex();
+            final int rhs = o2.getIndex();
+            return lhs < rhs ? -1 : +1;
+        }
+    };
+
+    /***************************************************************************
+     * * Private Fields * *
+     **************************************************************************/
+    private SpreadsheetView spreadSheetView;
+    /**
+     * Count the number of fixed Cell added to the viewport If we added just a
+     * few cells in the addLeadingCell We need to add the remaining in the
+     * addTrailingCell
+     */
+    private int cellFixedAdded = 0;
+    private boolean cellIndexCall = false;
+
+    /**
+     * The list of Rows fixed. It only contains the number of the rows, sorted.
+     */
+//    private final ArrayList<Integer> fixedRows = new ArrayList<Integer>();
+    // private double scrollY = 0;
+
+    /***************************************************************************
+     * * Constructor * *
+     **************************************************************************/
+    public GridVirtualFlow() {
+        super();
+        final ChangeListener<Number> listenerY = new ChangeListener<Number>() {
+            @Override
+            public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
+                layoutTotal();
+            }
+        };
+        getVbar().valueProperty().addListener(listenerY);
+
+    }
+
+    /***************************************************************************