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

org.controlsfx.control.spreadsheet.SpreadsheetCellEditor Maven / Gradle / Ivy

Go to download

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

There is a newer version: 11.2.1
Show newest version
/**
 * Copyright (c) 2013, 2014 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 org.controlsfx.control.spreadsheet;

import impl.org.controlsfx.i18n.Localization;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.time.LocalDate;
import java.util.List;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.DatePicker;
import javafx.scene.control.IndexRange;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;

/**
 * 
 * SpreadsheetCellEditor are used by {@link SpreadsheetCellType} and
 * {@link SpreadsheetCell} in order to control how each value will be entered. 
* *

General behavior:

Editors will be displayed if the user double-click * in an editable cell ( see {@link SpreadsheetCell#setEditable(boolean)} ).
* If the user does anything outside the editor, the editor will be forced * to try to commit the edition and close itself. If the value is not * valid, the editor will cancel the value and close itself. The editor is just * here to allow communication between the user and the {@link SpreadsheetView}. * It will just be given a value, and it will just give back another one after. * The policy regarding validation of a given value is defined in * {@link SpreadsheetCellType#match(Object)}. * * If the value doesn't meet the requirements when saving the cell, nothing * happens and the editor keeps editing.
* You can abandon a current modification by pressing "esc" key.
* * 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. *
*

Specific behavior:

This class offers some static classes in order to * create a {@link SpreadsheetCellEditor}. Here are their properties:
* *
    *
  • {@link StringEditor}: Basic {@link TextField}, can accept all data and * save it as a string.
  • *
  • {@link ListEditor}: Display a {@link ComboBox} with the different values. *
  • *
  • {@link DoubleEditor}: Display a {@link TextField} which accepts only * double value. If the entered value is incorrect, the background will turn red * so that the user will know in advance if the data will be saved or not.
  • *
  • {@link IntegerEditor}: Display a {@link TextField} which accepts only * Integer value. If the entered value is incorrect, the background will turn red * so that the user will know in advance if the data will be saved or not.
  • *
  • {@link DateEditor}: Display a {@link DatePicker}.
  • *
  • {@link ObjectEditor}: Display a {@link TextField} , accept an Object.
  • *
* *
*

Creating your editor:

You can of course create your own * {@link SpreadsheetCellEditor} for displaying other controls.
* * You just have to override the four abstract methods. Remember that you * will never call those methods directly. They will be called by the * {@link SpreadsheetView} when needed. *
    *
  • {@link #startEdit(Object)}: You will configure your control with the * given value which is {@link SpreadsheetCell#getItem()} converted to an * object. You do not instantiate your control here, you do it in the * constructor.
  • *
  • {@link #getEditor()}: You will return which control you're using (for * display).
  • *
  • {@link #getControlValue()}: You will return the value inside your editor * in order to submit it for validation.
  • *
  • {@link #end()}: When editing is finished, you can properly close your own * control.
  • *
*
* Keep in mind that you will interact only with {@link #endEdit(boolean)} where * a true value means you want to commit, and a false means you * want to cancel. The {@link SpreadsheetView} will handle all the rest for you * and call your methods at the right moment.
* *

Use case :

* *

Visual:

* * * * * * * * * * * * * * * * * *
String
List
Double
Date
* * * @see SpreadsheetView * @see SpreadsheetCell * @see SpreadsheetCellType */ public abstract class SpreadsheetCellEditor { private static final double MAX_EDITOR_HEIGHT = 50.0; private static final DecimalFormat decimalFormat = new DecimalFormat("#.##########"); //$NON-NLS-1$ SpreadsheetView view; /*************************************************************************** * * Constructor * * **************************************************************************/ /** * Construct the SpreadsheetCellEditor. * * @param view */ public SpreadsheetCellEditor(SpreadsheetView view) { this.view = view; } /*************************************************************************** * * Public Final Methods * * **************************************************************************/ /** * Whenever you want to stop the edition, you call that method.
* True means you're trying to commit the value, then * {@link SpreadsheetCellType#convertValue(Object)} will be called in order * to verify that the value is correct.
* False means you're trying to cancel the value and it will be follow by * {@link #end()}.
* See SpreadsheetCellEditor description * * @param b * true means commit, false means cancel */ public final void endEdit(boolean b) { view.getCellsViewSkin().getSpreadsheetCellEditorImpl().endEdit(b); } /*************************************************************************** * * Public Abstract Methods * * **************************************************************************/ /** * This method will be called when edition start.
* You will then do all the configuration of your editor. * * @param item */ public abstract void startEdit(Object item); /** * Return the control used for controlling the input. This is called at the * beginning in order to display your control in the cell. * * @return the control used. */ public abstract Control getEditor(); /** * Return the value within your editor as a string. This will be used by the * {@link SpreadsheetCellType#convertValue(Object)} in order to compute * whether the value is valid regarding the {@link SpreadsheetCellType} * policy. * * @return the value within your editor as a string. */ public abstract String getControlValue(); /** * This method will be called at the end of edition.
* You will be offered the possibility to do the configuration post editing. */ public abstract void end(); /*************************************************************************** * * Public Methods * * **************************************************************************/ /** * 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. */ public static class ObjectEditor extends SpreadsheetCellEditor { /*************************************************************************** * * Private Fields * * **************************************************************************/ private final TextField tf; /*************************************************************************** * * Constructor * * **************************************************************************/ /** * Constructor for the ObjectEditor.. * @param view The SpreadsheetView */ public ObjectEditor(SpreadsheetView view) { super(view); tf = new TextField(); } /*************************************************************************** * * Public Methods * * **************************************************************************/ @Override public void startEdit(Object value) { if (value instanceof String) { tf.setText(value.toString()); } attachEnterEscapeEventHandler(); tf.requestFocus(); tf.end(); } @Override public String getControlValue() { return tf.getText(); } @Override public void end() { tf.setOnKeyPressed(null); } @Override public TextField getEditor() { return tf; } /*************************************************************************** * * Private Methods * * **************************************************************************/ private void attachEnterEscapeEventHandler() { tf.setOnKeyPressed(new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { endEdit(true); } else if (t.getCode() == KeyCode.ESCAPE) { endEdit(false); } } }); } } /** * A {@link SpreadsheetCellEditor} for * {@link SpreadsheetCellType.StringType} typed cells. It displays a * {@link TextField} where the user can type different values. */ public static class StringEditor extends SpreadsheetCellEditor { /*************************************************************************** * * Private Fields * * **************************************************************************/ private final TextField tf; /*************************************************************************** * * Constructor * * **************************************************************************/ /** * Constructor for the StringEditor. * @param view The SpreadsheetView */ public StringEditor(SpreadsheetView view) { super(view); tf = new TextField(); } /*************************************************************************** * * Public Methods * * **************************************************************************/ @Override public void startEdit(Object value) { if (value instanceof String || value == null) { tf.setText((String) value); } attachEnterEscapeEventHandler(); tf.requestFocus(); tf.end(); } @Override public String getControlValue() { return tf.getText(); } @Override public void end() { tf.setOnKeyPressed(null); } @Override public TextField getEditor() { return tf; } /*************************************************************************** * * Private Methods * * **************************************************************************/ private void attachEnterEscapeEventHandler() { tf.setOnKeyPressed(new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { endEdit(true); } else if (t.getCode() == KeyCode.ESCAPE) { endEdit(false); } } }); } } /** * 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() { @Override public void handle(KeyEvent keyEvent) { if (keyEvent.getCode() == KeyCode.ENTER) { if (keyEvent.isShiftDown()) { //if shift is down, we insert a new line. textArea.replaceSelection("\n"); //$NON-NLS-1$ } else { endEdit(true); } } else if (keyEvent.getCode() == KeyCode.ESCAPE) { endEdit(false); } } }); } } /** * A {@link SpreadsheetCellEditor} for * {@link SpreadsheetCellType.DoubleType} typed cells. It displays a * {@link TextField} where the user can type different numbers. Only numbers * will be stored.
* Moreover, the {@link TextField} will turn red if the value currently * entered if incorrect. */ public static class DoubleEditor extends SpreadsheetCellEditor { /*************************************************************************** * * private Fields * * **************************************************************************/ private final TextField tf; /*************************************************************************** * * Constructor * * **************************************************************************/ /** * Constructor for the DoubleEditor. * @param view The SpreadsheetView. */ public DoubleEditor(SpreadsheetView view) { super(view); tf = new TextField() { @Override public void insertText(int index, String text) { String fixedText = fixText(text); super.insertText(index, fixedText); } @Override public void replaceText(int start, int end, String text) { String fixedText = fixText(text); super.replaceText(start, end, fixedText); } @Override public void replaceText(IndexRange range, String text) { replaceText(range.getStart(), range.getEnd(), text); } private String fixText(String text) { DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(Localization.getLocale()); text = text.replace(' ', '\u00a0');//$NON-NLS-1$ return text.replaceAll("\\.", Character.toString(symbols.getDecimalSeparator()));//$NON-NLS-1$ } }; } /*************************************************************************** * * Public Methods * * **************************************************************************/ /** {@inheritDoc} */ @Override public void startEdit(Object value) { if (value instanceof Double) { //We want to set the text in its proper form regarding the Locale. decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Localization.getLocale())); tf.setText(((Double) value).isNaN() ? "" : decimalFormat.format(value)); //$NON-NLS-1$ } else { tf.setText(null); } tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ attachEnterEscapeEventHandler(); tf.requestFocus(); tf.end(); } /** {@inheritDoc} */ @Override public void end() { tf.setOnKeyPressed(null); } /** {@inheritDoc} */ @Override public TextField getEditor() { return tf; } /** {@inheritDoc} */ @Override public String getControlValue() { NumberFormat format = NumberFormat.getInstance(Localization.getLocale()); ParsePosition parsePosition = new ParsePosition(0); if (tf.getText() != null) { Number number = format.parse(tf.getText(), parsePosition); if (number != null) { return String.valueOf(number.doubleValue()); } } return tf.getText(); } /*************************************************************************** * * Private Methods * * **************************************************************************/ private void attachEnterEscapeEventHandler() { tf.setOnKeyPressed(new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { try { if (tf.getText().equals("")) { //$NON-NLS-1$ endEdit(true); } else { tryParsing(); endEdit(true); } } catch (Exception e) { } } else if (t.getCode() == KeyCode.ESCAPE) { endEdit(false); } } }); tf.setOnKeyReleased(new EventHandler() { @Override public void handle(KeyEvent t) { try { if (tf.getText().equals("")) { //$NON-NLS-1$ tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ } else { tryParsing(); tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ } } catch (Exception e) { tf.getStyleClass().add("error"); //$NON-NLS-1$ } } }); } private void tryParsing() throws ParseException { NumberFormat format = NumberFormat.getNumberInstance(Localization.getLocale()); ParsePosition parsePosition = new ParsePosition(0); format.parse(tf.getText(), parsePosition); if (parsePosition.getIndex() != tf.getText().length()) { throw new ParseException("Invalid input", parsePosition.getIndex()); } } } /** * A {@link SpreadsheetCellEditor} for * {@link SpreadsheetCellType.DoubleType} typed cells. It displays a * {@link TextField} where the user can type different numbers. Only numbers * will be stored.
* Moreover, the {@link TextField} will turn red if the value currently * entered if incorrect. */ public static class IntegerEditor extends SpreadsheetCellEditor { /*************************************************************************** * * Private Fields * * **************************************************************************/ private final TextField tf; /*************************************************************************** * * Constructor * * **************************************************************************/ /** * Constructor for the IntegerEditor. * @param view the SpreadsheetView */ public IntegerEditor(SpreadsheetView view) { super(view); tf = new TextField(); } /*************************************************************************** * * Public Methods * * **************************************************************************/ /** {@inheritDoc} */ @Override public void startEdit(Object value) { if (value instanceof Integer) { tf.setText(Integer.toString((Integer) value)); } else { tf.setText(null); } tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ attachEnterEscapeEventHandler(); tf.requestFocus(); tf.end(); } /** {@inheritDoc} */ @Override public void end() { tf.setOnKeyPressed(null); } /** {@inheritDoc} */ @Override public TextField getEditor() { return tf; } /** {@inheritDoc} */ @Override public String getControlValue() { return tf.getText(); } /*************************************************************************** * * Private Methods * * **************************************************************************/ private void attachEnterEscapeEventHandler() { tf.setOnKeyPressed(new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { try { if (tf.getText().equals("")) { //$NON-NLS-1$ endEdit(true); } else { Integer.parseInt(tf.getText()); endEdit(true); } } catch (Exception e) { } } else if (t.getCode() == KeyCode.ESCAPE) { endEdit(false); } } }); tf.setOnKeyReleased(new EventHandler() { @Override public void handle(KeyEvent t) { try { if (tf.getText().equals("")) { //$NON-NLS-1$ tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ } else { Integer.parseInt(tf.getText()); tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ } } catch (Exception e) { tf.getStyleClass().add("error"); //$NON-NLS-1$ } } }); } } /** * * A {@link SpreadsheetCellEditor} for {@link SpreadsheetCellType.ListType} * typed cells. It displays a {@link ComboBox} where the user can choose a * value. */ public static class ListEditor extends SpreadsheetCellEditor { /*************************************************************************** * * Private Fields * * **************************************************************************/ private final List itemList; private final ComboBox cb; private String originalValue; /*************************************************************************** * * Constructor * * **************************************************************************/ /** * Constructor for the ListEditor. * @param view The SpreadsheetView * @param itemList The items to display in the editor. */ public ListEditor(SpreadsheetView view, final List itemList) { super(view); this.itemList = itemList; cb = new ComboBox(); cb.setVisibleRowCount(5); } /*************************************************************************** * * Public Methods * * **************************************************************************/ /** {@inheritDoc} */ @Override public void startEdit(Object value) { if (value instanceof String) { originalValue = value.toString(); } else { originalValue = null; } ObservableList items = FXCollections.observableList(itemList); cb.setItems(items); cb.setValue(originalValue); attachEnterEscapeEventHandler(); cb.show(); cb.requestFocus(); } /** {@inheritDoc} */ @Override public void end() { cb.setOnKeyPressed(null); } /** {@inheritDoc} */ @Override public ComboBox getEditor() { return cb; } /** {@inheritDoc} */ @Override public String getControlValue() { return cb.getSelectionModel().getSelectedItem(); } /*************************************************************************** * * Private Methods * * **************************************************************************/ private void attachEnterEscapeEventHandler() { cb.setOnKeyPressed(new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ESCAPE) { cb.setValue(originalValue); endEdit(false); } else if (t.getCode() == KeyCode.ENTER) { endEdit(true); } } }); } } /** * A {@link SpreadsheetCellEditor} for {@link SpreadsheetCellType.DateType} * typed cells. It displays a {@link DatePicker} where the user can choose a * date through a visual calendar. */ public static class DateEditor extends SpreadsheetCellEditor { /*************************************************************************** * * Private Fields * * **************************************************************************/ private final DatePicker datePicker; private EventHandler eh; private ChangeListener cl; /** * This is needed because "endEdit" will call our "end" method too late * when pressing enter, so several "endEdit" will be called. So this * prevent that to happen. */ private boolean ending = false; /*************************************************************************** * * Constructor * * **************************************************************************/ /** * Constructor for the DateEditor. * @param view the SpreadsheetView * @param converter A Converter for converting a date to a String. */ public DateEditor(SpreadsheetView view, StringConverter converter) { super(view); datePicker = new DatePicker(); datePicker.setConverter(converter); } /*************************************************************************** * * Public Methods * * **************************************************************************/ /** {@inheritDoc} */ @Override public void startEdit(Object value) { if (value instanceof LocalDate) { datePicker.setValue((LocalDate) value); } attachEnterEscapeEventHandler(); datePicker.show(); datePicker.getEditor().requestFocus(); } /** {@inheritDoc} */ @Override public void end() { if (datePicker.isShowing()) { datePicker.hide(); } datePicker.removeEventFilter(KeyEvent.KEY_PRESSED, eh); datePicker.valueProperty().removeListener(cl); } /** {@inheritDoc} */ @Override public DatePicker getEditor() { return datePicker; } /** {@inheritDoc} */ @Override public String getControlValue() { return datePicker.getEditor().getText(); } /*************************************************************************** * * Private Methods * * **************************************************************************/ private void attachEnterEscapeEventHandler() { /** * We need to add an EventFilter because otherwise the DatePicker * will block "escape" and "enter". But when "enter" is hit, we need * to runLater the commit because the value has not yet hit the * DatePicker itself. */ eh = new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { ending = true; endEdit(true); ending = false; } else if (t.getCode() == KeyCode.ESCAPE) { endEdit(false); } } }; datePicker.addEventFilter(KeyEvent.KEY_PRESSED, eh); cl = new ChangeListener() { @Override public void changed(ObservableValue arg0, LocalDate arg1, LocalDate arg2) { if (!ending) endEdit(true); } }; datePicker.valueProperty().addListener(cl); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy