diff --git a/src/com/olexyn/ensync/EditCell.java b/src/com/olexyn/ensync/EditCell.java new file mode 100644 index 0000000..c14e5f4 --- /dev/null +++ b/src/com/olexyn/ensync/EditCell.java @@ -0,0 +1,361 @@ +package com.olexyn.ensync; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.scene.control.*; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.util.Callback; +import javafx.util.StringConverter; +import javafx.util.converter.DefaultStringConverter; + +public class EditCell extends TextFieldTableCell { + + private TextField textField; + + private boolean escapePressed = false; + + private TablePosition tablePos = null; + + public EditCell(final StringConverter converter) { + + super(converter); + + } + + public static Callback, TableCell> forTableColumn() { + + return forTableColumn(new DefaultStringConverter()); + + } + + public static Callback, + + TableCell> forTableColumn( + + final StringConverter converter) { + + return list -> new EditCell(converter); + + } + + @Override + + public void startEdit() { + + if (!isEditable() || !getTableView().isEditable() || + + !getTableColumn().isEditable()) { + + return; + + } + + super.startEdit(); + + if (isEditing()) { + + if (textField == null) { + + textField = getTextField(); + + } + + escapePressed = false; + + startEdit(textField); + + final TableView table = getTableView(); + + tablePos = table.getEditingCell(); + + } + + } + + /** + * {@inheritDoc} + */ + + @Override + + public void commitEdit(T newValue) { + + if (!isEditing()) + + return; + + final TableView table = getTableView(); + + if (table != null) { + + // Inform the TableView of the edit being ready to be committed. + + TableColumn.CellEditEvent editEvent = new TableColumn.CellEditEvent(table, tablePos, + + TableColumn.editCommitEvent(), newValue); + + Event.fireEvent(getTableColumn(), editEvent); + + } + + // we need to setEditing(false): + + super.cancelEdit(); // this fires an invalid EditCancelEvent. + + // update the item within this cell, so that it represents the new value + + updateItem(newValue, false); + + if (table != null) { + + // reset the editing cell on the TableView + + table.edit(-1, null); + + } + + } + + /** + * {@inheritDoc} + */ + + @Override + + public void cancelEdit() { + + if (escapePressed) { + + // this is a cancel event after escape key + + super.cancelEdit(); + + setText(getItemText()); // restore the original text in the view + + } else { + + // this is not a cancel event after escape key + + // we interpret it as commit. + + String newText = textField.getText(); + + // commit the new text to the model + + this.commitEdit(getConverter().fromString(newText)); + + } + + setGraphic(null); // stop editing with TextField + + } + + /** + * {@inheritDoc} + */ + + @Override + + public void updateItem(T item, boolean empty) { + + super.updateItem(item, empty); + + updateItem(); + + } + + private TextField getTextField() { + + final TextField textField = new TextField(getItemText()); + + textField.setOnAction(new EventHandler() { + + @Override + public void handle(ActionEvent event) { + + System.out.println("hi"); + + } + + }); + + // Use onAction here rather than onKeyReleased (with check for Enter), + + textField.setOnAction(event -> { + + if (getConverter() == null) { + + throw new IllegalStateException("StringConverter is null."); + + } + + this.commitEdit(getConverter().fromString(textField.getText())); + + event.consume(); + + }); + + textField.focusedProperty().addListener(new ChangeListener() { + + + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + + if (!newValue) { + + commitEdit(getConverter().fromString(textField.getText())); + + } + + } + + }); + + textField.setOnKeyPressed(t -> { + + if (t.getCode() == KeyCode.ESCAPE) + + escapePressed = true; + + else + + escapePressed = false; + + }); + + textField.setOnKeyReleased(t -> { + + if (t.getCode() == KeyCode.ESCAPE) { + + throw new IllegalArgumentException( + + "did not expect esc key releases here."); + + } + + }); + + textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + + if (event.getCode() == KeyCode.ESCAPE) { + + textField.setText(getConverter().toString(getItem())); + + cancelEdit(); + + event.consume(); + + } else if (event.getCode() == KeyCode.RIGHT || + + event.getCode() == KeyCode.TAB) { + + getTableView().getSelectionModel().selectNext(); + + event.consume(); + + } else if (event.getCode() == KeyCode.LEFT) { + + getTableView().getSelectionModel().selectPrevious(); + + event.consume(); + + } else if (event.getCode() == KeyCode.UP) { + + getTableView().getSelectionModel().selectAboveCell(); + + event.consume(); + + } else if (event.getCode() == KeyCode.DOWN) { + + getTableView().getSelectionModel().selectBelowCell(); + + event.consume(); + + } + + }); + + return textField; + + } + + private String getItemText() { + + return getConverter() == null ? + + getItem() == null ? "" : getItem().toString() : + + getConverter().toString(getItem()); + + } + + private void updateItem() { + + if (isEmpty()) { + + setText(null); + + setGraphic(null); + + } else { + + if (isEditing()) { + + if (textField != null) { + + textField.setText(getItemText()); + + } + + setText(null); + + setGraphic(textField); + + } else { + + setText(getItemText()); + + setGraphic(null); + + } + + } + + } + + private void startEdit(final TextField textField) { + + if (textField != null) { + + textField.setText(getItemText()); + + } + + setText(null); + + setGraphic(textField); + + textField.selectAll(); + + // requesting focus so that key input can immediately go into the + + // TextField + + textField.requestFocus(); + + } + +} diff --git a/src/com/olexyn/ensync/MyDateStringConverter.java b/src/com/olexyn/ensync/MyDateStringConverter.java new file mode 100644 index 0000000..4788e3d --- /dev/null +++ b/src/com/olexyn/ensync/MyDateStringConverter.java @@ -0,0 +1,35 @@ +package com.olexyn.ensync; + +import javafx.util.converter.DateStringConverter; + +import java.util.Date; + +public class MyDateStringConverter extends DateStringConverter { + + public MyDateStringConverter(final String pattern) { + + super(pattern); + + } + + @Override + + public Date fromString(String value) { + + // catches the RuntimeException thrown by + + // DateStringConverter.fromString() + + try { + + return super.fromString(value); + + } catch (RuntimeException ex) { + + return null; + + } + + } + +} diff --git a/src/com/olexyn/ensync/MyDoubleStringConverter.java b/src/com/olexyn/ensync/MyDoubleStringConverter.java new file mode 100644 index 0000000..7618122 --- /dev/null +++ b/src/com/olexyn/ensync/MyDoubleStringConverter.java @@ -0,0 +1,35 @@ +package com.olexyn.ensync; + +import javafx.util.converter.DoubleStringConverter; + +public class MyDoubleStringConverter extends DoubleStringConverter { + + @Override + + public Double fromString(final String value) { + + return value.isEmpty() || !isNumber(value) ? null : + + super.fromString(value); + + } + + public boolean isNumber(String value) { + + int size = value.length(); + + for (int i = 0; i < size; i++) { + + if (!Character.isDigit(value.charAt(i))) { + + return false; + + } + + } + + return size > 0; + + } + +} diff --git a/src/com/olexyn/ensync/Person.java b/src/com/olexyn/ensync/Person.java new file mode 100644 index 0000000..f28f266 --- /dev/null +++ b/src/com/olexyn/ensync/Person.java @@ -0,0 +1,44 @@ +package com.olexyn.ensync; + +import java.util.Date; + +public class Person { + + private String firstName; + private String surname; + private Date date; + private String occupation; + private double salary; + + + + + Person(String firstName, String surname, Date date, String occupation, double salary){ + this.firstName = firstName; + this.surname = surname; + this.date = date; + this.occupation = occupation; + this.salary = salary; + + } + + public double getSalary(){ + return 0.0; + } + + public Date getDateOfBirth(){ + return null; + } + + public String getFirstName(){ + return "firstName"; + } + + public String getSurname(){ + return "surname"; + } + + public String getOccupation(){ + return "occupation"; + } +} diff --git a/src/com/olexyn/ensync/PersonTableData.java b/src/com/olexyn/ensync/PersonTableData.java new file mode 100644 index 0000000..1cb0ec2 --- /dev/null +++ b/src/com/olexyn/ensync/PersonTableData.java @@ -0,0 +1,118 @@ +package com.olexyn.ensync; + +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.scene.control.TableColumn; + +import java.util.Date; + +public class PersonTableData { + + private SimpleStringProperty firstName; + + private SimpleStringProperty surname; + + private SimpleObjectProperty dateOfBirth; + + private SimpleStringProperty occupation; + + private SimpleDoubleProperty salary; + + // added to create the model from the Person object, which might be data retrieved from a database + + public PersonTableData(Person person) { + + this.firstName = new SimpleStringProperty(person.getFirstName()); + + this.surname = new SimpleStringProperty(person.getSurname()); + + this.dateOfBirth = new SimpleObjectProperty < Date > (person.getDateOfBirth()); + + this.occupation = new SimpleStringProperty(person.getOccupation()); + + this.salary = new SimpleDoubleProperty(person.getSalary()); + + } + + public PersonTableData(final String firstName, final String surname, + + final Date dateOfBirth, final String occupation, + + final double salary) { + + this.firstName = new SimpleStringProperty(firstName); + + this.surname = new SimpleStringProperty(surname); + + this.dateOfBirth = new SimpleObjectProperty < Date > (dateOfBirth); + + this.occupation = new SimpleStringProperty(occupation); + + this.salary = new SimpleDoubleProperty(salary); + + } + + public String getFirstName() { + + return firstName.get(); + + } + + public void setFirstName(final String firstName) { + + this.firstName.set(firstName); + + } + + public String getSurname() { + + return surname.get(); + + } + + public void setSurname(final String surname) { + + this.surname.set(surname); + + } + + public Date getDateOfBirth() { + + return dateOfBirth.get(); + + } + + public void setDateOfBirth(final Date dateOfBirth) { + + this.dateOfBirth.set(dateOfBirth); + + } + + public String getOccupation() { + + return occupation.get(); + + } + + public void setOccupation(final String occupation) { + + this.occupation.set(occupation); + + } + + public double getSalary() { + + return salary.get(); + + } + + public void setSalary(final double salary) { + + this.salary.set(salary); + + } + + + +} \ No newline at end of file diff --git a/src/com/olexyn/ensync/TableAppController.java b/src/com/olexyn/ensync/TableAppController.java new file mode 100644 index 0000000..3847236 --- /dev/null +++ b/src/com/olexyn/ensync/TableAppController.java @@ -0,0 +1,335 @@ +package com.olexyn.ensync; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; + +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +public class TableAppController implements Initializable { + + private static final String DATE_PATTERN = "dd/MM/yyyy"; + private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_PATTERN); + + @FXML + private TableView table; + @FXML + private TextField firstNameTextField; + @FXML + private TextField surnameTextField; + @FXML + private TextField dateOfBirthTextField; + @FXML + private TextField occupationTextField; + @FXML + private TextField salaryTextField; + @FXML + private Button submitButton; + private ObservableList data = FXCollections + + .observableArrayList(); + @FXML + + private TableColumn dateOfBirthColumn; + + @FXML + + private TableColumn salaryColumn; + + @Override + public void initialize(final URL url, final ResourceBundle rb) { + + DATE_FORMATTER.setLenient(false); + + table.setItems(data); + + populate(retrieveData()); + + setupDateOfBirthColumn(); + + setupSalaryColumn(); + + setTableEditable(); + + } + + private List retrieveData() { + + try { + + return Arrays.asList(new Person("Dan", "Newton", DATE_FORMATTER.parse("06/01/1994"), "Java Developer", 22000), new Person("George", "Newton", DATE_FORMATTER.parse("24/01/1995"), "Bro", 15021), new Person("Laura", "So", DATE_FORMATTER.parse("24/04/1995"), "Student", 0), new Person("Jamie", "Harwood", DATE_FORMATTER.parse("15/12/9999"), "Java Developer", 30000), new Person("Michael", "Collins", DATE_FORMATTER.parse("01/01/0001"), "Developer", 299), new Person("Stuart", "Kerrigan", DATE_FORMATTER.parse("06/10/1894"), "Teaching Fellow", 100000)); + } catch (ParseException e) { + + e.printStackTrace(); + + } + + return new ArrayList(); + + } + + private void populate(final List people) { + + people.forEach(p -> data.add(new PersonTableData(p))); + + } + + private void setupDateOfBirthColumn() { + + // formats the display value to display dates in the form of dd/MM/yyyy + + dateOfBirthColumn.setCellFactory(EditCell.forTableColumn(new MyDateStringConverter(DATE_PATTERN))); + + // updates the dateOfBirth field on the com.olexyn.ensync.PersonTableData object to the + + // committed value + + dateOfBirthColumn.setOnEditCommit(event -> { + + final Date value = event.getNewValue() != null ? event.getNewValue() : + + event.getOldValue(); + + ((PersonTableData) event.getTableView().getItems() + + .get(event.getTablePosition().getRow())) + + .setDateOfBirth(value); + + table.refresh(); + + }); + + } + + private void setupSalaryColumn() { + + // sets the cell factory to use EditCell which will handle key presses + + // and firing commit events + + salaryColumn.setCellFactory( + + EditCell.forTableColumn( + + new MyDoubleStringConverter())); + + // updates the salary field on the com.olexyn.ensync.PersonTableData object to the + + // committed value + + salaryColumn.setOnEditCommit(event -> { + + final Double value = event.getNewValue() != null ? + + event.getNewValue() : event.getOldValue(); + + ((PersonTableData) event.getTableView().getItems() + + .get(event.getTablePosition().getRow())).setSalary(value); + + table.refresh(); + + }); + + } + + private void setTableEditable() { + + table.setEditable(true); + + // allows the individual cells to be selected + + table.getSelectionModel().cellSelectionEnabledProperty().set(true); + + // when character or numbers pressed it will start edit in editable + + // fields + + table.setOnKeyPressed(event -> { + + if (event.getCode().isLetterKey() || event.getCode().isDigitKey()) { + + editFocusedCell(); + + } else if (event.getCode() == KeyCode.RIGHT || + + event.getCode() == KeyCode.TAB) { + + table.getSelectionModel().selectNext(); + + event.consume(); + + } else if (event.getCode() == KeyCode.LEFT) { + + // work around due to + + // TableView.getSelectionModel().selectPrevious() due to a bug + + // stopping it from working on + + // the first column in the last row of the table + + selectPrevious(); + + event.consume(); + + } + + }); + + } + + @SuppressWarnings("unchecked") + + private void editFocusedCell() { + + final TablePosition focusedCell = table + + .focusModelProperty().get().focusedCellProperty().get(); + + table.edit(focusedCell.getRow(), focusedCell.getTableColumn()); + + } + + @SuppressWarnings("unchecked") + + private void selectPrevious() { + + if (table.getSelectionModel().isCellSelectionEnabled()) { + + // in cell selection mode, we have to wrap around, going from + + // right-to-left, and then wrapping to the end of the previous line + + TablePosition pos = table.getFocusModel() + + .getFocusedCell(); + + if (pos.getColumn() - 1 >= 0) { + + // go to previous row + + table.getSelectionModel().select(pos.getRow(), + + getTableColumn(pos.getTableColumn(), -1)); + + } else if (pos.getRow() < table.getItems().size()) { + + // wrap to end of previous row + + table.getSelectionModel().select(pos.getRow() - 1, + + table.getVisibleLeafColumn( + + table.getVisibleLeafColumns().size() - 1)); + + } + + } else { + + int focusIndex = table.getFocusModel().getFocusedIndex(); + + if (focusIndex == -1) { + + table.getSelectionModel().select(table.getItems().size() - 1); + + } else if (focusIndex > 0) { + + table.getSelectionModel().select(focusIndex - 1); + + } + + } + + } + + private TableColumn getTableColumn( + + final TableColumn column, int offset) { + + int columnIndex = table.getVisibleLeafIndex(column); + + int newColumnIndex = columnIndex + offset; + + return table.getVisibleLeafColumn(newColumnIndex); + + } + + @FXML + + private void submit(final ActionEvent event) { + + if (allFieldsValid()) { + + final String firstName = firstNameTextField.getText(); + + final String surname = surnameTextField.getText(); + + Date dateOfBirth = null; + + try { + + dateOfBirth = DATE_FORMATTER + + .parse(dateOfBirthTextField.getText()); + + } catch (final ParseException e) {} + + final String occupation = occupationTextField.getText(); + + final double salary = Double.parseDouble(salaryTextField.getText()); + + data.add(new PersonTableData(firstName, surname, dateOfBirth, + + occupation, salary)); + + } + + } + + private boolean allFieldsValid() { + + return !firstNameTextField.getText().isEmpty() && + + !surnameTextField.getText().isEmpty() && + + dateOfBirthFieldValid() && + + !occupationTextField.getText().isEmpty() && + + !salaryTextField.getText().isEmpty(); + + } + + private boolean dateOfBirthFieldValid() { + + if (!dateOfBirthTextField.getText().isEmpty()) { + + try { + + DATE_FORMATTER.parse(dateOfBirthTextField.getText()); + + return true; + + } catch (ParseException e) { + + return false; + + } + + } + + return false; + + } + + +} \ No newline at end of file diff --git a/src/com/olexyn/ensync/TableLogic.java b/src/com/olexyn/ensync/TableLogic.java new file mode 100644 index 0000000..4389828 --- /dev/null +++ b/src/com/olexyn/ensync/TableLogic.java @@ -0,0 +1,3 @@ +package com.olexyn.ensync; + +public class TableLogic {}