From aa72658e6e15f13146b4e53efa91b63c0ef5bbc3 Mon Sep 17 00:00:00 2001 From: Jan Schraff Date: Mon, 20 Jul 2020 09:28:04 +0200 Subject: [PATCH] extracted ProjectSelectionBox Logic into its own class to enable reuseability --- ...DoesProjectMatchSearchFilterPredicate.java | 31 +++ .../keeptime/view/ManageWorkController.java | 197 +---------------- .../common/SearchableProjectCombobox.java | 202 ++++++++++++++++++ src/main/resources/layouts/manage-work.fxml | 3 +- 4 files changed, 240 insertions(+), 193 deletions(-) create mode 100644 src/main/java/de/doubleslash/keeptime/common/DoesProjectMatchSearchFilterPredicate.java create mode 100644 src/main/java/de/doubleslash/keeptime/view/common/SearchableProjectCombobox.java diff --git a/src/main/java/de/doubleslash/keeptime/common/DoesProjectMatchSearchFilterPredicate.java b/src/main/java/de/doubleslash/keeptime/common/DoesProjectMatchSearchFilterPredicate.java new file mode 100644 index 00000000..805b4ef2 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/common/DoesProjectMatchSearchFilterPredicate.java @@ -0,0 +1,31 @@ +package de.doubleslash.keeptime.common; + +import java.util.function.Predicate; + +import de.doubleslash.keeptime.model.Project; + +public class DoesProjectMatchSearchFilterPredicate implements Predicate { + + private final String searchText; + + public DoesProjectMatchSearchFilterPredicate(final String searchText) { + this.searchText = searchText; + } + + @Override + public boolean test(final Project project) { + // If filter text is empty, display all data. + if (searchText == null || searchText.isEmpty()) { + return true; + } + + final String lowerCaseFilter = searchText.toLowerCase(); + + if (project.getName().toLowerCase().contains(lowerCaseFilter) + || project.getDescription().toLowerCase().contains(lowerCaseFilter)) { + return true; + } + + return false; + } +} diff --git a/src/main/java/de/doubleslash/keeptime/view/ManageWorkController.java b/src/main/java/de/doubleslash/keeptime/view/ManageWorkController.java index a61bd480..53d02459 100644 --- a/src/main/java/de/doubleslash/keeptime/view/ManageWorkController.java +++ b/src/main/java/de/doubleslash/keeptime/view/ManageWorkController.java @@ -24,33 +24,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin; - -import de.doubleslash.keeptime.common.ColorHelper; -import de.doubleslash.keeptime.common.StyleUtils; import de.doubleslash.keeptime.model.Model; -import de.doubleslash.keeptime.model.Project; import de.doubleslash.keeptime.model.Work; -import javafx.application.Platform; +import de.doubleslash.keeptime.view.common.SearchableProjectCombobox; import javafx.beans.property.StringProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.collections.transformation.FilteredList; import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.control.ComboBox; import javafx.scene.control.DatePicker; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; import javafx.scene.control.Spinner; import javafx.scene.control.SpinnerValueFactory; import javafx.scene.control.TextArea; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyEvent; import javafx.scene.layout.GridPane; -import javafx.scene.paint.Color; -import javafx.util.StringConverter; import javafx.util.converter.LocalTimeStringConverter; public class ManageWorkController { @@ -78,17 +61,11 @@ public class ManageWorkController { private TextArea noteTextArea; @FXML - private ComboBox projectComboBox; - - private boolean comboChange; - private Project selectedProject; - - private FilteredList filteredList; + private SearchableProjectCombobox projectComboBox; public void setModel(final Model model) { this.model = model; - filteredList = new FilteredList<>(model.getSortedAvailableProjects()); - projectComboBox.setItems(filteredList); + projectComboBox.setProjects(model.getSortedAvailableProjects()); } @FXML @@ -98,9 +75,6 @@ private void initialize() { setUpTimeSpinner(endTimeSpinner); - setProjectUpComboBox(); - - Platform.runLater(() -> projectComboBox.requestFocus()); } private void setUpTimeSpinner(final Spinner spinner) { @@ -146,136 +120,9 @@ public void increment(final int steps) { } - private void setProjectUpComboBox() { - // color Dropdown Options - projectComboBox.setCellFactory(listView -> new ListCell() { - - @Override - protected void updateItem(final Project project, final boolean empty) { - super.updateItem(project, empty); - if (project == null || empty) { - setGraphic(null); - } else { - setColor(this, model.hoverBackgroundColor.get()); - - setTextFill(project.getColor()); - setText(project.getName()); - - setUnderline(project.isWork()); - } - } - }); - - // set text of selected value - projectComboBox.setConverter(new StringConverter() { - @Override - public String toString(final Project project) { - if (project == null) { - return null; - } else { - return project.getName(); - } - } - - @Override - public Project fromString(final String string) { - // ignores String and gets selected Value of ComboBox - return projectComboBox.getValue(); - } - }); - - // needs to set again because editable is ignored from fxml because of custom preselection of current Project - projectComboBox.setEditable(true); - - projectComboBox.valueProperty().addListener( - (final ObservableValue observable, final Project oldValue, final Project newValue) -> { - if (newValue == null) { - return; - } - - selectedProject = newValue; - comboChange = true; - // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 - Platform.runLater(() -> { - setTextColor(projectComboBox.getEditor(), newValue.getColor()); - }); - } - - ); - - enableStrgA_combo(); - - projectComboBox.getEditor().textProperty().addListener(new ChangeListener() { - - boolean isValidProject = true; - - @Override - public void changed(final ObservableValue observable, final String oldValue, - final String newValue) { - - // ignore selectionChanges - if (comboChange) { - comboChange = false; - isValidProject = true; - return; - } - - // is necessary to not autoselect same Project if Project was selected - if (isValidProject) { - isValidProject = false; - projectComboBox.getSelectionModel().clearSelection(); - // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 - Platform.runLater(() -> { - setTextColor(projectComboBox.getEditor(), model.hoverFontColor.get()); - }); - } - - // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 - Platform.runLater(() -> { - projectComboBox.hide(); - - final String searchText = projectComboBox.getEditor().getText(); - filteredList.setPredicate( - project -> ProjectsListViewController.doesProjectMatchSearchFilter(searchText, project)); - - if (projectComboBox.getEditor().focusedProperty().get()) { - projectComboBox.show(); - } - - }); - - } - }); - - // manages Focusbehaviour - projectComboBox.getEditor().focusedProperty().addListener((final ObservableValue observable, - final Boolean oldIsFocused, final Boolean newIsFocused) -> { - if (newIsFocused) { - // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 - Platform.runLater(() -> projectComboBox.getEditor().selectAll()); - } else { - // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 - Platform.runLater(() -> projectComboBox.hide()); - } - - }); - - // on - projectComboBox.setOnKeyReleased(ke -> { - if (ke.getCode() == KeyCode.ENTER && projectComboBox.getSelectionModel().isEmpty()) { - if (!projectComboBox.getItems().isEmpty()) { - projectComboBox.getSelectionModel().selectFirst(); - comboChange = true; - } - } - - }); - - } - public void initializeWith(final Work work) { LOG.info("Setting values."); - selectedProject = work.getProject(); + projectComboBox.setProject(work.getProject(), model.hoverBackgroundColor.get(), model.hoverFontColor.get()); startDatePicker.setValue(work.getStartTime().toLocalDate()); endDatePicker.setValue(work.getEndTime().toLocalDate()); @@ -284,47 +131,13 @@ public void initializeWith(final Work work) { noteTextArea.setText(work.getNotes()); - projectComboBox.getSelectionModel().select(work.getProject()); - - setColor(projectComboBox, model.hoverBackgroundColor.get()); - setColor(projectComboBox.getEditor(), model.hoverBackgroundColor.get()); - - setTextColor(projectComboBox.getEditor(), model.hoverFontColor.get()); } public Work getWorkFromUserInput() { return new Work(startDatePicker.getValue(), LocalDateTime.of(startDatePicker.getValue(), startTimeSpinner.getValue()), - LocalDateTime.of(endDatePicker.getValue(), endTimeSpinner.getValue()), selectedProject, + LocalDateTime.of(endDatePicker.getValue(), endTimeSpinner.getValue()), projectComboBox.getValue(), noteTextArea.getText()); } - private void enableStrgA_combo() { - // strg+a Behaviour bug hack - // https://stackoverflow.com/questions/51943654/javafx-combobox-make-control-a-select-all-in-text-box-while-dropdown-is-visi - projectComboBox.setOnShown(e -> { - final ComboBoxListViewSkin skin = (ComboBoxListViewSkin) projectComboBox.getSkin(); - final ListView list = (ListView) skin.getPopupContent(); - final KeyCodeCombination ctrlA = new KeyCodeCombination(KeyCode.A, KeyCodeCombination.CONTROL_DOWN); - list.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> { - if (ctrlA.match(keyEvent)) { - projectComboBox.getEditor().selectAll(); - } - }); - projectComboBox.setOnShown(null); - }); - } - - private void setColor(final Node object, final Color color) { - final String style = StyleUtils.changeStyleAttribute(object.getStyle(), "fx-background-color", - "rgba(" + ColorHelper.colorToCssRgba(color) + ")"); - object.setStyle(style); - } - - private void setTextColor(final Node object, final Color color) { - final String style = StyleUtils.changeStyleAttribute(object.getStyle(), "fx-text-fill", - "rgba(" + ColorHelper.colorToCssRgba(color) + ")"); - object.setStyle(style); - } - } diff --git a/src/main/java/de/doubleslash/keeptime/view/common/SearchableProjectCombobox.java b/src/main/java/de/doubleslash/keeptime/view/common/SearchableProjectCombobox.java new file mode 100644 index 00000000..1f215863 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/view/common/SearchableProjectCombobox.java @@ -0,0 +1,202 @@ +package de.doubleslash.keeptime.view.common; + +import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin; + +import de.doubleslash.keeptime.common.ColorHelper; +import de.doubleslash.keeptime.common.DoesProjectMatchSearchFilterPredicate; +import de.doubleslash.keeptime.common.StyleUtils; +import de.doubleslash.keeptime.model.Project; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.paint.Color; +import javafx.util.StringConverter; + +public class SearchableProjectCombobox extends ComboBox { + + private boolean comboChange; + private Project selectedProject; + private Color fontColor; + FilteredList filteredList; + + public SearchableProjectCombobox() { + super(); + + final SearchableProjectCombobox comboBox = this; + + // strg+a Behaviour bug hack + // https://stackoverflow.com/questions/51943654/javafx-combobox-make-control-a-select-all-in-text-box-while-dropdown-is-visi + this.setOnShown(e -> { + final ComboBoxListViewSkin skin = (ComboBoxListViewSkin) comboBox.getSkin(); + final ListView list = (ListView) skin.getPopupContent(); + final KeyCodeCombination ctrlA = new KeyCodeCombination(KeyCode.A, KeyCodeCombination.CONTROL_DOWN); + list.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> { + if (ctrlA.match(keyEvent)) { + this.getEditor().selectAll(); + } + }); + this.setOnShown(null); + }); + + // color Dropdown Options + this.setCellFactory(listView -> new ListCell() { + @Override + protected void updateItem(final Project project, final boolean empty) { + super.updateItem(project, empty); + if (project == null || empty) { + setGraphic(null); + } else { + + setTextFill(project.getColor()); + setText(project.getName()); + + setUnderline(project.isWork()); + } + } + }); + + // set text of selected value + this.setConverter(new StringConverter() { + @Override + public String toString(final Project project) { + if (project == null) { + return null; + } else { + return project.getName(); + } + } + + @Override + public Project fromString(final String string) { + // ignores String and gets selected Value of ComboBox + return comboBox.getValue(); + } + }); + + this.valueProperty().addListener( + (final ObservableValue observable, final Project oldValue, final Project newValue) -> { + if (newValue == null) { + return; + } + + selectedProject = newValue; + comboChange = true; + // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 + Platform.runLater(() -> { + setTextColor(this.getEditor(), newValue.getColor()); + }); + } + + ); + + this.getEditor().textProperty().addListener(new ChangeListener() { + + boolean isValidProject = true; + + @Override + public void changed(final ObservableValue observable, final String oldValue, + final String newValue) { + + // ignore selectionChanges + if (comboChange) { + comboChange = false; + isValidProject = true; + return; + } + + // is necessary to not autoselect same Project if Project was selected + if (isValidProject) { + isValidProject = false; + comboBox.getSelectionModel().clearSelection(); + // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 + Platform.runLater(() -> { + setTextColor(comboBox.getEditor(), fontColor); + }); + } + + // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 + Platform.runLater(() -> { + comboBox.hide(); + + final String searchText = comboBox.getEditor().getText(); + filteredList.setPredicate(new DoesProjectMatchSearchFilterPredicate(searchText)); + + if (comboBox.getEditor().focusedProperty().get() && !isValidProject) { + comboBox.show(); + } + + }); + + } + }); + + // manages Focusbehaviour + this.getEditor().focusedProperty().addListener((final ObservableValue observable, + final Boolean oldIsFocused, final Boolean newIsFocused) -> { + if (newIsFocused) { + // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 + Platform.runLater(() -> comboBox.getEditor().selectAll()); + } else { + // needed to avoid exception on empty textfield https://bugs.openjdk.java.net/browse/JDK-8081700 + Platform.runLater(() -> comboBox.hide()); + } + + }); + + // on + this.setOnKeyReleased(ke -> { + if (ke.getCode() == KeyCode.ENTER && this.getSelectionModel().isEmpty()) { + if (!this.getItems().isEmpty()) { + this.getSelectionModel().selectFirst(); + comboChange = true; + } + } + + }); + + Platform.runLater(() -> this.requestFocus()); + } + + public Project getSelectedValue() { + return selectedProject; + + } + + private void setColor(final Node object, final Color color) { + final String style = StyleUtils.changeStyleAttribute(object.getStyle(), "fx-background-color", + "rgba(" + ColorHelper.colorToCssRgba(color) + ")"); + object.setStyle(style); + } + + private void setTextColor(final Node object, final Color color) { + final String style = StyleUtils.changeStyleAttribute(object.getStyle(), "fx-text-fill", + "rgba(" + ColorHelper.colorToCssRgba(color) + ")"); + object.setStyle(style); + } + + public void setProject(final Project project, final Color background, final Color font) { + + this.fontColor = font; + selectedProject = project; + setColor(this, background); + setColor(this.getEditor(), background); + + setTextColor(this.getEditor(), project.getColor()); + + this.getSelectionModel().select(project); + } + + public void setProjects(final ObservableList projects) { + filteredList = new FilteredList<>(projects); + super.setItems(filteredList); + } +} diff --git a/src/main/resources/layouts/manage-work.fxml b/src/main/resources/layouts/manage-work.fxml index 6171b112..8d1acd8c 100644 --- a/src/main/resources/layouts/manage-work.fxml +++ b/src/main/resources/layouts/manage-work.fxml @@ -10,6 +10,7 @@ + @@ -42,7 +43,7 @@ - +