Commits

Samir Hadzic  committed 66aa05a

Adding a TextAreaEditor in SpreadsheetView. Several bugs highlighted with this introduction have been fixed:
Pressing Enter at the end of the Grid with a RowSpan cell don't fire exception anymore.
Editors can now have maximum height (useful for textArea), default is provided.
TextField editor have transparent background for better integration in the Grid.
TextArea enter will commit the value, and CTRL+ENTER will go to the next line in the editor.
When scrolling to the very bottom of the Grid, TableCell were not well replaced in GridCellEditor and causes the editor not to respond fully with span. This is now fixed.

  • Participants
  • Parent commits debafed

Comments (0)

Files changed (6)

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

                 // already on the cell and we wanted to go below.
                 if (!spreadsheetView.isPressed() && oldPosition.getColumn() == newPosition.getColumn() && oldPosition.getRow() == newPosition.getRow() - 1) {
                     Platform.runLater(() -> {
-                        tfm.focus(getTableRowSpan(oldPosition, cellsView), oldPosition.getTableColumn());
+                        tfm.focus(getNextRowNumber(oldPosition, cellsView), oldPosition.getTableColumn());
                     });
 
                 } else {
      * the RowSpan to be on a visible Cell)
      *
      * @param t
-     * @param spreadsheetView
+     * @param cellsView
      * @return
      */
-    static int getTableRowSpan(final TablePosition<?, ?> t, SpreadsheetGridView cellsView) {
+    public static int getNextRowNumber(final TablePosition<?, ?> t, SpreadsheetGridView cellsView) {
         return cellsView.getItems().get(t.getRow()).get(t.getColumn()).getRowSpan()
                 + cellsView.getItems().get(t.getRow()).get(t.getColumn()).getRow();
     }

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

     
     //The last key pressed in order to select cell below if it was "enter"
     private KeyCode lastKeyPressed;
-    private static final double MAX_EDITOR_HEIGHT = 50.0;
 
     /***************************************************************************
      * * Constructor * *
                    TablePosition<ObservableList<SpreadsheetCell>, ?> position = (TablePosition<ObservableList<SpreadsheetCell>, ?>) handle.getGridView().
                             getFocusModel().getFocusedCell();
                     if (position != null) {
-                        handle.getGridView().getSelectionModel().clearAndSelect(position.getRow() + 1, position.getTableColumn());
+                        
+                        handle.getGridView().getSelectionModel().clearAndSelect(FocusModelListener.getNextRowNumber(position,handle.getGridView()), position.getTableColumn());
                     }
                 }
             }
 
         // Then we call the user editor in order for it to be ready
         Object value = modelCell.getItem();
-        Double maxHeight = Math.max(handle.getCellsViewSkin().getRowHeight(viewCell.getIndex()), MAX_EDITOR_HEIGHT);
+        Double maxHeight = Math.max(handle.getCellsViewSkin().getRowHeight(viewCell.getIndex()), spreadsheetCellEditor.getMaxHeight());
         spreadsheetCellEditor.getEditor().setMaxHeight(maxHeight);
         spreadsheetCellEditor.getEditor().setPrefWidth(viewCell.getWidth());
         
         private boolean addCell(CellView cell) {
             GridRow lastRow = handle.getCellsViewSkin().getRow(getCellCount() - 1);
 
-            // If the row returned is the extra one at the bottom (see RT-31503)
+            /**
+             * When we scroll to the very bottom, we may have several null/blank
+             * cell beyond the rowCount. So we must iterate over the cell
+             * backward till we found the last one suiting our needs.
+             * (see RT-31503)
+             */
             if (lastRow.getIndex() >= handle.getView().getGrid().getRowCount()) {
-                lastRow = handle.getCellsViewSkin().getRow(handle.getView().getGrid().getRowCount() - 1);
+                lastRow = null;
+                int i = handle.getView().getGrid().getRowCount()-1;
+                while(i> 0 && (lastRow == null || lastRow.getIndex() >= handle.getView().getGrid().getRowCount())){
+                    lastRow = handle.getCellsViewSkin().getRow(i--);
+                }
             }
 
             if (lastRow != null) {

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

 import javafx.animation.KeyFrame;
 import javafx.animation.Timeline;
 import javafx.application.Platform;
+import javafx.beans.NamedArg;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
 import javafx.collections.WeakListChangeListener;
      * @param spreadsheetView
      * @param cellsView 
      */
-    public SpreadsheetViewSelectionModel(SpreadsheetView spreadsheetView, SpreadsheetGridView cellsView) {
+    public SpreadsheetViewSelectionModel(@NamedArg("spreadsheetView") SpreadsheetView spreadsheetView,@NamedArg("cellsView") SpreadsheetGridView cellsView) {
         super(cellsView);
         this.cellsView = cellsView;
         this.spreadsheetView = spreadsheetView;
         // Variable we need for algorithm
         TablePosition<ObservableList<SpreadsheetCell>, ?> posFinal = new TablePosition<>(getTableView(), row,
                 column);
-
+        
         final SpreadsheetView.SpanType spanType = spreadsheetView.getSpanType(row, posFinal.getColumn());
 
+        //For some reasons (see below), we don't want to go in edition.
+        boolean edition=true;
         /**
          * We check if we are on covered cell. If so we have the algorithm of
          * the focus model to give the selection to the right cell.
          */
         switch (spanType) {
             case ROW_SPAN_INVISIBLE:
-                // If we notice that the new selected cell is the previous one,
-                // then it means that we were
-                // already on the cell and we wanted to go below.
-                // We make sure that old is not null, and that the move is
-                // initiated by keyboard.
-                // Because if it's a click, then we just want to go on the
-                // clicked cell (not below)
+                /**
+                 * If we notice that the new selected cell is the previous one,
+                 * then it means that we were already on the cell and we wanted
+                 * to go below. We make sure that old is not null, and that the
+                 * move is initiated by keyboard. Because if it's a click, then
+                 * we just want to go on the clicked cell (not below)
+                 */
                 if (old != null && key && !shift && old.getColumn() == posFinal.getColumn()
                         && old.getRow() == posFinal.getRow() - 1) {
-                    posFinal = getVisibleCell(FocusModelListener.getTableRowSpan(old, cellsView), old.getTableColumn(), old.getColumn());
-                } else {
-                    // If the current selected cell if hidden by row span, we go
-                    // above
-                    posFinal = getVisibleCell(row, column, posFinal.getColumn());
+                    int visibleRow = FocusModelListener.getNextRowNumber(old, cellsView);
+                    /**
+                     * If the visibleRow we're targeting is out of bounds, we do
+                     * not want to get a visibleCell, so we step out. But we
+                     * need to set edition to false because we will be going
+                     * back to the old cell and we could go to edition.
+                     */
+                    if (visibleRow < getItemCount()) {
+                        posFinal = getVisibleCell(visibleRow, old.getTableColumn(), old.getColumn());
+                        break;
+                    }else{
+                        edition = false;
+                    }
                 }
+                // If the current selected cell if hidden by row span, we go
+                // above
+                posFinal = getVisibleCell(row, column, posFinal.getColumn());
                 break;
             case BOTH_INVISIBLE:
                 // If the current selected cell if hidden by a both (row and
         }
 
         // This is to handle edition
-        if (posFinal.equals(old) && !ctrl && !shift && !drag) {
+        if (edition && posFinal.equals(old) && !ctrl && !shift && !drag) {
             // If we are on an Invisible row or both (in diagonal), we need
             // to force the edition
             if (spanType == SpreadsheetView.SpanType.ROW_SPAN_INVISIBLE

File controlsfx/src/main/java/org/controlsfx/control/spreadsheet/SpreadsheetCellEditor.java

 import java.text.DecimalFormat;
 import java.time.LocalDate;
 import java.util.List;
-
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.scene.control.ComboBox;
 import javafx.scene.control.Control;
 import javafx.scene.control.DatePicker;
+import javafx.scene.control.TextArea;
 import javafx.scene.control.TextField;
 import javafx.scene.input.KeyCode;
 import javafx.scene.input.KeyEvent;
  * happens and the editor keeps editing. <br/>
  * You can abandon a current modification by pressing "esc" key. <br/>
  * 
+ * You can specify a maximum height to your spreadsheetCellEditor with {@link #getMaxHeight()
+ * }. This can be used in order to control the display of your editor. If they
+ * should grow or not in a big cell. (for example a {@link TextAreaEditor} want
+ * to grow with the cell in order to take full space for display.
+ * <br/>
  * <h3>Specific behavior:</h3> This class offers some static classes in order to
  * create a {@link SpreadsheetCellEditor}. Here are their properties: <br/>
  * 
  * @see SpreadsheetCellType
  */
 public abstract class SpreadsheetCellEditor {
+    private static final double MAX_EDITOR_HEIGHT = 50.0;
+    
     public static final DecimalFormat decimalFormat=new DecimalFormat("#.##########");
     SpreadsheetView view;
 
     public abstract void end();
 
     /**
+     * Return the maximum height of the editor. 
+     * @return 50 by default.
+     */
+    public double getMaxHeight(){
+        return MAX_EDITOR_HEIGHT;
+    }
+    
+    /**
      * A {@link SpreadsheetCellEditor} for
      * {@link SpreadsheetCellType.ObjectType} typed cells. It displays a
      * {@link TextField} where the user can type different values.
         }
     }
 
+    
+    /**
+     * A {@link SpreadsheetCellEditor} for
+     * {@link SpreadsheetCellType.StringType} typed cells. It displays a
+     * {@link TextField} where the user can type different values.
+     */
+    public static class TextAreaEditor extends SpreadsheetCellEditor {
+
+        /**
+         * *************************************************************************
+         * * Private Fields * *
+         * ************************************************************************
+         */
+        private final TextArea textArea;
+
+        /**
+         * *************************************************************************
+         * * Constructor * *
+         * ************************************************************************
+         */
+        /**
+         * Constructor for the StringEditor.
+         *
+         * @param view The SpreadsheetView
+         */
+        public TextAreaEditor(SpreadsheetView view) {
+            super(view);
+            textArea = new TextArea();
+        }
+
+        /**
+         * *************************************************************************
+         * * Public Methods * *
+         * ************************************************************************
+         */
+        @Override
+        public void startEdit(Object value) {
+            if (value instanceof String || value == null) {
+                textArea.setText((String) value);
+            }
+            attachEnterEscapeEventHandler();
+
+            textArea.requestFocus();
+            textArea.end();
+        }
+
+        @Override
+        public String getControlValue() {
+            return textArea.getText();
+        }
+
+        @Override
+        public void end() {
+            textArea.setOnKeyPressed(null);
+        }
+
+        @Override
+        public TextArea getEditor() {
+            return textArea;
+        }
+
+        @Override
+        public double getMaxHeight() {
+            return 500;
+        }
+        /**
+         * *************************************************************************
+         * * Private Methods * *
+         * ************************************************************************
+         */
+        private void attachEnterEscapeEventHandler() {
+            textArea.setOnKeyPressed(new EventHandler<KeyEvent>() {
+                @Override
+                public void handle(KeyEvent keyEvent) {
+                    if (keyEvent.getCode() == KeyCode.ENTER) {
+                        if (keyEvent.isShortcutDown()) {
+                            //if shortcut is down, we insert a new line.
+                            textArea.replaceSelection("\n");
+                        } else {
+                            endEdit(true);
+                        }
+                    } else if (keyEvent.getCode() == KeyCode.ESCAPE) {
+                        endEdit(false);
+                    }
+                }
+            });
+        }
+    }
     /**
      * A {@link SpreadsheetCellEditor} for
      * {@link SpreadsheetCellType.DoubleType} typed cells. It displays a

File controlsfx/src/main/java/org/controlsfx/control/spreadsheet/SpreadsheetView.java

                 else if (keyEvent.isShortcutDown() && keyEvent.getCode()==KeyCode.V)
                     pasteClipboard();
                 // Go to the next row
-                else if (keyEvent.getCode() == KeyCode.ENTER) {
+                else if (!keyEvent.isShortcutDown() && keyEvent.getCode() == KeyCode.ENTER) {
                     cellsView.setEditWithEnter(true);
                     TablePosition<ObservableList<SpreadsheetCell>, ?> position = (TablePosition<ObservableList<SpreadsheetCell>, ?>) cellsView
                             .getFocusModel().getFocusedCell();
                     if (position != null) {
-                        cellsView.getSelectionModel().clearAndSelect(position.getRow() + 1, position.getTableColumn());
+                        cellsView.getSelectionModel().clearAndSelect(FocusModelListener.getNextRowNumber(position, getCellsView()), position.getTableColumn());
                     }
                    /* // Go to next cell
                 } else if (keyEvent.getCode().compareTo(KeyCode.TAB) == 0) {

File controlsfx/src/main/resources/org/controlsfx/control/spreadsheet/spreadsheet.css

     -fx-shape : "M 0 0 L 1 0 L 0 -1 z";
 }
 
-/* TextField must squeeze tight, so no padding
-on top and bottom */
-.spreadsheet-cell TextField{
-    -fx-padding : 0 7 0 7 ;
-}
-
 .indicationLabel{
     -fx-font-style : italic;
 }
     -fx-effect:dropshadow(gaussian, black, 10, 0.1, 0, 0);
     -fx-cursor:hand;
 }
+
+/* We do not want to show the textfield, we want an Excel visual */
+CellView > .text-input.text-field{
+    -fx-padding : 0 0 0 -0.2em;
+    -fx-background-color: transparent;
+}