From b3364d40bff3b726e8ac26e9f73621e4a7dc839a Mon Sep 17 00:00:00 2001 From: Phillip Ortmann Date: Mon, 20 Feb 2023 11:48:08 +0100 Subject: [PATCH 01/20] #11: Newline at end of pom file --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bd473af..36691fd 100644 --- a/pom.xml +++ b/pom.xml @@ -184,4 +184,4 @@ - \ No newline at end of file + From 0d55f8c6d5daa5281333f3f810ae0760ffc38d00 Mon Sep 17 00:00:00 2001 From: Phillip Ortmann Date: Mon, 20 Feb 2023 15:52:55 +0100 Subject: [PATCH 02/20] #11: First sorting mechanism --- .../keeptask/view/ViewController.java | 182 ++++++++++++------ src/main/resources/layouts/ViewLayout.fxml | 19 +- 2 files changed, 139 insertions(+), 62 deletions(-) diff --git a/src/main/java/de/doubleslash/keeptask/view/ViewController.java b/src/main/java/de/doubleslash/keeptask/view/ViewController.java index d9d7be9..2022536 100644 --- a/src/main/java/de/doubleslash/keeptask/view/ViewController.java +++ b/src/main/java/de/doubleslash/keeptask/view/ViewController.java @@ -17,34 +17,35 @@ package de.doubleslash.keeptask.view; import de.doubleslash.keeptask.common.*; +import de.doubleslash.keeptask.controller.Controller; import de.doubleslash.keeptask.exceptions.FXMLLoaderException; +import de.doubleslash.keeptask.model.Model; +import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; +import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.stage.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import de.doubleslash.keeptask.controller.Controller; -import de.doubleslash.keeptask.model.Model; -import javafx.fxml.FXML; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.stage.Stage; - import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -54,62 +55,45 @@ public class ViewController { private static final Logger LOG = LoggerFactory.getLogger(ViewController.class); private final Model model; private final Controller controller; - - private class Delta { - double x; - double y; - } - private final Delta dragDelta = new Delta(); + private final List> timeFilters = new ArrayList<>(); + private final List projectNameFilters = new ArrayList<>(); private Stage mainStage; - @FXML private Pane pane; - @FXML private HBox projectFilterHbox; - @FXML private Button minimizeButton; - @FXML private Button closeButton; - @FXML private TextField searchTextInput; - @FXML private ToggleButton todayToggleButton; - @FXML private ToggleButton tomorrowToggleButton; - @FXML private ToggleButton expiredToggleButton; - @FXML private CheckBox alsoCompletedCheckbox; - @FXML private TextField prioTextInput; - @FXML private TextField projectTextInput; - @FXML private TextField todoTextInput; - @FXML private DatePicker dueDatePicker; - @FXML private Button addTodoButton; - @FXML private VBox workItemVBox; - - private final List> timeFilters = new ArrayList<>(); - private final List projectNameFilters = new ArrayList<>(); + @FXML + private HBox sortingCriteriaHBox; + @FXML + private ComboBox addSortingCriteriaCbx; + private ObservableList sortingCriteriaList = FXCollections.observableArrayList(); @Autowired public ViewController(final Model model, final Controller controller) { @@ -117,6 +101,17 @@ public ViewController(final Model model, final Controller controller) { this.controller = controller; } + public static Function orderBy(SortingCriteria criteria) { + switch (criteria) { + case Priority: + return WorkItem::getPriority; + case DueDate: + return WorkItem::getDueDateTime; + default: + throw new IllegalArgumentException("" + criteria); + } + } + @FXML private void initialize() { @@ -144,7 +139,8 @@ private void initialize() { if (dueDate != null) { dueDateTime = dueDate.atStartOfDay(); } - WorkItem newItem = new WorkItem(projectTextInput.getText(), prioTextInput.getText(), todoTextInput.getText(), LocalDateTime.now(), dueDateTime, null, false, ""); + WorkItem newItem = new WorkItem(projectTextInput.getText(), prioTextInput.getText(), todoTextInput.getText(), + LocalDateTime.now(), dueDateTime, null, false, ""); controller.addWorkItem(newItem); todoTextInput.clear(); @@ -161,14 +157,50 @@ private void initialize() { alsoCompletedCheckbox.setOnAction(actionEvent -> refreshTodos()); - todayToggleButton.setUserData((Predicate) workItem -> workItem.getDueDateTime() == null ? false : LocalDate.now().isEqual(workItem.getDueDateTime().toLocalDate())); + todayToggleButton.setUserData((Predicate) workItem -> workItem.getDueDateTime() == null ? + false : + LocalDate.now().isEqual(workItem.getDueDateTime().toLocalDate())); todayToggleButton.setOnAction(toggleButtonPressedAction()); - tomorrowToggleButton.setUserData((Predicate) workItem -> workItem.getDueDateTime() == null ? false : LocalDate.now().plusDays(1).isEqual(workItem.getDueDateTime().toLocalDate())); + tomorrowToggleButton.setUserData((Predicate) workItem -> workItem.getDueDateTime() == null ? + false : + LocalDate.now().plusDays(1).isEqual(workItem.getDueDateTime().toLocalDate())); tomorrowToggleButton.setOnAction(toggleButtonPressedAction()); - expiredToggleButton.setUserData((Predicate) workItem -> workItem.getDueDateTime() == null ? false : LocalDate.now().isAfter(workItem.getDueDateTime().toLocalDate())); + expiredToggleButton.setUserData((Predicate) workItem -> workItem.getDueDateTime() == null ? + false : + LocalDate.now().isAfter(workItem.getDueDateTime().toLocalDate())); expiredToggleButton.setOnAction(toggleButtonPressedAction()); + + sortingCriteriaList.addListener((ListChangeListener) change -> { + if (!change.next()) return; + + Button sortingCriteriaButton = new Button(); + String sortingCriteria = change.getAddedSubList().get(0).toString(); + sortingCriteriaButton.setText(sortingCriteria); + Tooltip tooltip = new Tooltip(); + tooltip.setText("Click to remove " + sortingCriteria + " sorting criteria"); + sortingCriteriaButton.setTooltip(tooltip); + sortingCriteriaButton.setOnAction(event -> { + sortingCriteriaList.remove(SortingCriteria.valueOf(sortingCriteria)); + sortingCriteriaHBox.getChildren().remove(sortingCriteriaButton); + refreshTodos(); + }); + sortingCriteriaHBox.getChildren().add(sortingCriteriaHBox.getChildren().size() - 1, sortingCriteriaButton); + refreshTodos(); + }); + + addSortingCriteriaCbx.setItems(FXCollections.observableArrayList(SortingCriteria.values())); + addSortingCriteriaCbx.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if(newValue == null) return; + + sortingCriteriaList.add((SortingCriteria) newValue); + addSortingCriteriaCbx.getSelectionModel().clearSelection(); + }); + } + + private void addSortingCriteria(String sortingCriteriaString) { + sortingCriteriaList.add(SortingCriteria.valueOf(sortingCriteriaString)); } private EventHandler toggleButtonPressedAction() { @@ -183,34 +215,39 @@ private EventHandler toggleButtonPressedAction() { }; } - private void updateProjectFilterButtons() { - Set projectNames = model.getWorkItems().stream().map(workItem -> workItem.getProject()).collect(Collectors.toSet()); + Set projectNames = model.getWorkItems() + .stream() + .map(workItem -> workItem.getProject()) + .collect(Collectors.toSet()); projectFilterHbox.getChildren().clear(); // recreate buttons projectFilterHbox.getChildren().addAll(projectNames.stream().map(projectName -> { - ToggleButton button = new ToggleButton(projectName); - button.setUserData(projectName); - button.setSelected(projectNameFilters.contains(projectName)); - - button.setOnAction(actionEvent -> { - ToggleButton sourceToggleButton = (ToggleButton) actionEvent.getSource(); - if (sourceToggleButton.isSelected()) { - projectNameFilters.add(projectName); - } else { - projectNameFilters.remove(projectName); - } - projectTextInput.setText(projectName); - refreshTodos(); - }); - return button; - } - ).collect(Collectors.toList()) - ); + ToggleButton button = new ToggleButton(projectName); + button.setUserData(projectName); + button.setSelected(projectNameFilters.contains(projectName)); + + button.setOnAction(actionEvent -> { + ToggleButton sourceToggleButton = (ToggleButton) actionEvent.getSource(); + if (sourceToggleButton.isSelected()) { + projectNameFilters.add(projectName); + } else { + projectNameFilters.remove(projectName); + } + projectTextInput.setText(projectName); + refreshTodos(); + }); + return button; + }).collect(Collectors.toList())); // reset previous filter - only keep the ones which still exist - List tempProjectNameFilters = projectNameFilters.stream().filter(projectFilter -> projectNames.stream().anyMatch(projectName -> projectFilter.equals(projectName))).collect(Collectors.toList()); + List tempProjectNameFilters = projectNameFilters.stream() + .filter(projectFilter -> projectNames.stream() + .anyMatch( + projectName -> projectFilter.equals( + projectName))) + .collect(Collectors.toList()); projectNameFilters.clear(); projectNameFilters.addAll(tempProjectNameFilters); } @@ -225,10 +262,24 @@ private void refreshTodos() { if (!timeFilters.isEmpty()) filteredWorkItemStream = filteredWorkItemStream.filter(timeFilters.stream().reduce(x -> false, Predicate::or)); if (!projectNameFilters.isEmpty()) - filteredWorkItemStream = filteredWorkItemStream.filter(workItem -> projectNameFilters.stream().anyMatch(filter -> workItem.getProject().equals(filter))); - filteredWorkItemStream = filteredWorkItemStream.filter(workItem -> alsoCompletedCheckbox.isSelected() ? true : !workItem.isFinished()); + filteredWorkItemStream = filteredWorkItemStream.filter( + workItem -> projectNameFilters.stream().anyMatch(filter -> workItem.getProject().equals(filter))); + filteredWorkItemStream = filteredWorkItemStream.filter( + workItem -> alsoCompletedCheckbox.isSelected() ? true : !workItem.isFinished()); + + Stream sortedFilteredWorkItemStream = filteredWorkItemStream; + Comparator comparator = null; + if (sortingCriteriaList.size() > 0) { + comparator = Comparator.comparing(ViewController.orderBy(sortingCriteriaList.get(0))); + } + for (int i = 1; i < sortingCriteriaList.size(); i++) { + SortingCriteria sortingCriteria = sortingCriteriaList.get(i); + comparator = comparator.thenComparing(ViewController.orderBy(sortingCriteria)); + } - List filteredItems = filteredWorkItemStream.collect(Collectors.toList()); + sortedFilteredWorkItemStream = + comparator == null ? sortedFilteredWorkItemStream : sortedFilteredWorkItemStream.sorted(comparator); + List filteredItems = sortedFilteredWorkItemStream.collect(Collectors.toList()); for (WorkItem workItem : filteredItems) { Node todoNode = createTodoNode(workItem); @@ -340,7 +391,8 @@ private void editTodoClicked(WorkItem workItem) { try { grid = loader.load(); } catch (final IOException e) { - throw new FXMLLoaderException(String.format("Error while loading '%s'.", Resources.RESOURCE.FXML_EDIT_WORKITEM_LAYOUT), e); + throw new FXMLLoaderException( + String.format("Error while loading '%s'.", Resources.RESOURCE.FXML_EDIT_WORKITEM_LAYOUT), e); } EditWorkItemController editWorkItemController = loader.getController(); editWorkItemController.initializeWith(workItem); @@ -389,4 +441,14 @@ public void setMainStage(Stage mainStage) { mainStage.sizeToScene(); } + private enum SortingCriteria { + Priority, + DueDate; + } + + private class Delta { + double x; + double y; + } + } diff --git a/src/main/resources/layouts/ViewLayout.fxml b/src/main/resources/layouts/ViewLayout.fxml index 6c48c68..6f7f327 100644 --- a/src/main/resources/layouts/ViewLayout.fxml +++ b/src/main/resources/layouts/ViewLayout.fxml @@ -1,16 +1,19 @@ + + + - + @@ -59,7 +62,19 @@ - + + + + + + + + + + + + + From 63337eb0696b17e7d110ab630a5e48b6c1c94e25 Mon Sep 17 00:00:00 2001 From: phigegner Date: Tue, 28 Feb 2023 09:29:34 +0100 Subject: [PATCH 03/20] #11: Code formatting --- .../keeptask/view/MainWindowController.java | 57 +++++++------------ 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java b/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java index aa58a10..fa58c49 100644 --- a/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java +++ b/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java @@ -19,7 +19,6 @@ import de.doubleslash.keeptask.common.*; import de.doubleslash.keeptask.controller.Controller; import de.doubleslash.keeptask.exceptions.FXMLLoaderException; - import de.doubleslash.keeptask.model.Model; import de.doubleslash.keeptask.model.TodoPart; import de.doubleslash.keeptask.model.WorkItem; @@ -46,7 +45,9 @@ import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.*; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -58,7 +59,7 @@ public class MainWindowController { private final Model model; private final Controller controller; - + SortedList sortedWorkItems; private Point dragDelta = new Point(0, 0); private Stage mainStage; @FXML @@ -67,10 +68,8 @@ public class MainWindowController { private Button minimizeButton; @FXML private Button closeButton; - @FXML private VBox filterVBox; - // TODO extract new ToDo-section into own controller @FXML private TextField prioTextInput; @@ -82,20 +81,15 @@ public class MainWindowController { private DatePicker dueDatePicker; @FXML private Button addTodoButton; - @FXML private HBox sortingCriteriaHBox; @FXML private ComboBox addSortingCriteriaCbx; private ObservableList sortingCriteriaList = FXCollections.observableArrayList(); - - // TODO extract TODO ListView to own controller @FXML private VBox workItemVBox; - SortedList sortedWorkItems; - @Autowired public MainWindowController(final Model model, final Controller controller) { this.model = model; @@ -149,8 +143,7 @@ private void initialize() { if (dueDate != null) { dueDateTime = dueDate.atStartOfDay(); } - WorkItem newItem = new WorkItem(projectTextInput.getText(), prioTextInput.getText(), todoTextInput.getText(), - LocalDateTime.now(), dueDateTime, null, false, ""); + WorkItem newItem = new WorkItem(projectTextInput.getText(), prioTextInput.getText(), todoTextInput.getText(), LocalDateTime.now(), dueDateTime, null, false, ""); controller.addWorkItem(newItem); todoTextInput.clear(); @@ -181,7 +174,7 @@ private void initialize() { addSortingCriteriaCbx.setItems(FXCollections.observableArrayList(SortingCriteria.values())); addSortingCriteriaCbx.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - if(newValue == null) return; + if (newValue == null) return; sortingCriteriaList.add((SortingCriteria) newValue); addSortingCriteriaCbx.getSelectionModel().clearSelection(); @@ -198,7 +191,7 @@ private void addSortingCriteria(String sortingCriteriaString) { sortingCriteriaList.add(SortingCriteria.valueOf(sortingCriteriaString)); } - private void loadFiltersLayout() { + private void loadFiltersLayout() { final FXMLLoader loader = FxmlLayout.createLoaderFor(Resources.RESOURCE.FXML_FILTER_LAYOUT); final VBox filterVBox; @@ -226,8 +219,7 @@ private void refreshTodos() { comparator = comparator.thenComparing(MainWindowController.orderBy(sortingCriteria)); } - sortedFilteredWorkItemStream = - comparator == null ? sortedFilteredWorkItemStream : sortedFilteredWorkItemStream.sorted(comparator); + sortedFilteredWorkItemStream = comparator == null ? sortedFilteredWorkItemStream : sortedFilteredWorkItemStream.sorted(comparator); List filteredItems = sortedFilteredWorkItemStream.collect(Collectors.toList()); for (WorkItem workItem : filteredItems) { @@ -235,8 +227,7 @@ private void refreshTodos() { children.add(todoNode); } - if (mainStage != null) - mainStage.sizeToScene(); + if (mainStage != null) mainStage.sizeToScene(); } private Node createTodoNode(WorkItem workItem) { @@ -254,12 +245,9 @@ private Node createTodoNode(WorkItem workItem) { hbox2.disableProperty().bind(completedCheckBox.selectedProperty()); ObservableList children1 = hbox2.getChildren(); Label prioLabel = new Label(workItem.getPriority()); - if (workItem.getPriority().equalsIgnoreCase("High")) - prioLabel.setTextFill(Color.RED); - if (workItem.getPriority().equalsIgnoreCase("Medium")) - prioLabel.setTextFill(Color.ORANGE); - if (!workItem.getPriority().isEmpty()) - children1.add(prioLabel); + if (workItem.getPriority().equalsIgnoreCase("High")) prioLabel.setTextFill(Color.RED); + if (workItem.getPriority().equalsIgnoreCase("Medium")) prioLabel.setTextFill(Color.ORANGE); + if (!workItem.getPriority().isEmpty()) children1.add(prioLabel); Label projectLabel = new Label(workItem.getProject()); children1.add(projectLabel); @@ -283,12 +271,10 @@ private Node createTodoNode(WorkItem workItem) { children1.add(todoHbox); Label dueDateTimeLabel = new Label("Due: " + workItem.getDueDateTime()); - if (workItem.getDueDateTime() != null) - children1.add(dueDateTimeLabel); + if (workItem.getDueDateTime() != null) children1.add(dueDateTimeLabel); Label noteLabel = new Label("Note: " + workItem.getNote()); - if (!workItem.getNote().isEmpty()) - children1.add(noteLabel); + if (!workItem.getNote().isEmpty()) children1.add(noteLabel); Label completedDateTimeLabel = new Label("Completed: " + workItem.getCompletedDateTime()); if (workItem.isFinished()) // TODO this is only working on rerender @@ -296,16 +282,14 @@ private Node createTodoNode(WorkItem workItem) { children.add(hbox2); - Button editButton = new Button("", - SvgNodeProvider.getSvgNodeWithScale(Resources.RESOURCE.SVG_PENCIL_ICON, 0.03, 0.03)); + Button editButton = new Button("", SvgNodeProvider.getSvgNodeWithScale(Resources.RESOURCE.SVG_PENCIL_ICON, 0.03, 0.03)); editButton.setMaxSize(20, 18); editButton.setMinSize(20, 18); editButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); children.add(editButton); editButton.setOnAction((actionEvent) -> editTodoClicked(workItem)); - Button deleteButton = new Button("", - SvgNodeProvider.getSvgNodeWithScale(Resources.RESOURCE.SVG_TRASH_ICON, 0.03, 0.03)); + Button deleteButton = new Button("", SvgNodeProvider.getSvgNodeWithScale(Resources.RESOURCE.SVG_TRASH_ICON, 0.03, 0.03)); deleteButton.setMaxSize(20, 18); deleteButton.setMinSize(20, 18); deleteButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); @@ -341,8 +325,7 @@ private void editTodoClicked(WorkItem workItem) { try { grid = loader.load(); } catch (final IOException e) { - throw new FXMLLoaderException( - String.format("Error while loading '%s'.", Resources.RESOURCE.FXML_EDIT_WORKITEM_LAYOUT), e); + throw new FXMLLoaderException(String.format("Error while loading '%s'.", Resources.RESOURCE.FXML_EDIT_WORKITEM_LAYOUT), e); } EditWorkItemController editWorkItemController = loader.getController(); editWorkItemController.initializeWith(workItem); @@ -365,8 +348,7 @@ private void editTodoClicked(WorkItem workItem) { private void runUpdateMainBackgroundColor() { Color color = model.defaultBackgroundColor.get(); - String style = StyleUtils.changeStyleAttribute(pane.getStyle(), "fx-background-color", - "rgba(" + ColorHelper.colorToCssRgba(color) + ")"); + String style = StyleUtils.changeStyleAttribute(pane.getStyle(), "fx-background-color", "rgba(" + ColorHelper.colorToCssRgba(color) + ")"); pane.setStyle(style); } @@ -392,7 +374,6 @@ public void setMainStage(Stage mainStage) { } private enum SortingCriteria { - Priority, - DueDate; + Priority, DueDate; } } From e48f8ac0299265e680af58da2e296064b7b1b3e5 Mon Sep 17 00:00:00 2001 From: phigegner Date: Tue, 28 Feb 2023 13:34:30 +0100 Subject: [PATCH 04/20] #11: Refactor filtering and extract sorting to dedicated controller --- .../keeptask/common/Resources.java | 14 +-- .../keeptask/controller/Controller.java | 16 +-- .../de/doubleslash/keeptask/model/Model.java | 23 +--- .../keeptask/view/FilterController.java | 27 ++--- .../keeptask/view/MainWindowController.java | 104 +++++------------- .../keeptask/view/SortingController.java | 101 +++++++++++++++++ .../resources/layouts/MainWindowLayout.fxml | 36 ++++++ src/main/resources/layouts/SortingLayout.fxml | 23 ++++ src/main/resources/layouts/ViewLayout.fxml | 55 --------- 9 files changed, 217 insertions(+), 182 deletions(-) create mode 100644 src/main/java/de/doubleslash/keeptask/view/SortingController.java create mode 100644 src/main/resources/layouts/MainWindowLayout.fxml create mode 100644 src/main/resources/layouts/SortingLayout.fxml delete mode 100644 src/main/resources/layouts/ViewLayout.fxml diff --git a/src/main/java/de/doubleslash/keeptask/common/Resources.java b/src/main/java/de/doubleslash/keeptask/common/Resources.java index 5928114..609c894 100644 --- a/src/main/java/de/doubleslash/keeptask/common/Resources.java +++ b/src/main/java/de/doubleslash/keeptask/common/Resources.java @@ -24,6 +24,10 @@ private Resources() { throw new IllegalStateException("Utility class"); } + public static URL getResource(final RESOURCE resource) { + return Resources.class.getResource(resource.getResourceLocation()); + } + public enum RESOURCE { /** * FONTS @@ -35,16 +39,16 @@ public enum RESOURCE { * LAYOUTS **/ // main - FXML_VIEW_LAYOUT("/layouts/ViewLayout.fxml"), + FXML_VIEW_LAYOUT("/layouts/MainWindowLayout.fxml"), FXML_EDIT_WORKITEM_LAYOUT("/layouts/EditWorkItemDialog.fxml"), FXML_FILTER_LAYOUT("/layouts/FiltersLayout.fxml"), + FXML_SORTING_LAYOUT("/layouts/SortingLayout.fxml"), SVG_TRASH_ICON("/svgs/trash-can.svg"), SVG_PENCIL_ICON("/svgs/pencil.svg"), - ICON_MAIN("/icons/icon.png") - ; + ICON_MAIN("/icons/icon.png"); String resourceLocation; @@ -56,8 +60,4 @@ public String getResourceLocation() { return resourceLocation; } } - - public static URL getResource(final RESOURCE resource) { - return Resources.class.getResource(resource.getResourceLocation()); - } } diff --git a/src/main/java/de/doubleslash/keeptask/controller/Controller.java b/src/main/java/de/doubleslash/keeptask/controller/Controller.java index fa966ae..4c56084 100644 --- a/src/main/java/de/doubleslash/keeptask/controller/Controller.java +++ b/src/main/java/de/doubleslash/keeptask/controller/Controller.java @@ -16,12 +16,7 @@ package de.doubleslash.keeptask.controller; -import java.time.LocalDateTime; -import java.util.List; -import java.util.function.Predicate; - -import javax.annotation.PreDestroy; - +import de.doubleslash.keeptask.model.Model; import de.doubleslash.keeptask.model.WorkItem; import de.doubleslash.keeptask.model.repos.WorkItemRepository; import org.slf4j.Logger; @@ -29,7 +24,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import de.doubleslash.keeptask.model.Model; +import javax.annotation.PreDestroy; +import java.time.LocalDateTime; +import java.util.List; @Service public class Controller { @@ -95,11 +92,6 @@ public void shutdown() { LOG.info("Controller shutdown"); } - public void setFilterPredicate(Predicate filterPredicate) { - LOG.debug("Filters were changed"); - model.getWorkFilteredList().setPredicate(filterPredicate); - } - public void setLatestSelectedProject(String projectName) { model.setLatestSelectedProject(projectName); } diff --git a/src/main/java/de/doubleslash/keeptask/model/Model.java b/src/main/java/de/doubleslash/keeptask/model/Model.java index 584986f..5d9ab01 100644 --- a/src/main/java/de/doubleslash/keeptask/model/Model.java +++ b/src/main/java/de/doubleslash/keeptask/model/Model.java @@ -16,35 +16,26 @@ package de.doubleslash.keeptask.model; -import de.doubleslash.keeptask.model.repos.WorkItemRepository; -import javafx.beans.InvalidationListener; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; +import javafx.scene.paint.Color; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javafx.scene.paint.Color; - import java.util.List; @Component public class Model { - public static final Color ORIGINAL_DEFAULT_BACKGROUND_COLOR = Color.WHITE; public final ObjectProperty defaultBackgroundColor = new SimpleObjectProperty<>( ORIGINAL_DEFAULT_BACKGROUND_COLOR); private ObservableList workItems = FXCollections.observableArrayList(); - private FilteredList workFilteredItems = new FilteredList(workItems); - private StringProperty latestSelectedProject = new SimpleStringProperty(); @Autowired @@ -52,21 +43,15 @@ public Model() { super(); } - public void setWorkItems(List workItems) { - this.workItems.clear(); - this.workItems.addAll(workItems); - } - public ObservableList getWorkItems() { return FXCollections.unmodifiableObservableList(workItems); } - public ObservableList getWorkFilteredItems() { - return FXCollections.unmodifiableObservableList(workFilteredItems); + public void setWorkItems(List workItems) { + this.workItems.clear(); + this.workItems.addAll(workItems); } - public FilteredList getWorkFilteredList(){return workFilteredItems;} - public StringProperty latestSelectedProjectProperty() { return latestSelectedProject; } diff --git a/src/main/java/de/doubleslash/keeptask/view/FilterController.java b/src/main/java/de/doubleslash/keeptask/view/FilterController.java index 2def301..36b4eb7 100644 --- a/src/main/java/de/doubleslash/keeptask/view/FilterController.java +++ b/src/main/java/de/doubleslash/keeptask/view/FilterController.java @@ -4,6 +4,7 @@ import de.doubleslash.keeptask.model.Model; import de.doubleslash.keeptask.model.WorkItem; import javafx.collections.ListChangeListener; +import javafx.collections.transformation.FilteredList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; @@ -20,35 +21,28 @@ import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; @Component public class FilterController { + private final Model model; + private final Controller controller; + private final List> timeFilters = new ArrayList<>(); + private final List projectNameFilters = new ArrayList<>(); @FXML private TextField searchTextInput; - @FXML private ToggleButton todayToggleButton; - @FXML private ToggleButton tomorrowToggleButton; - @FXML private ToggleButton expiredToggleButton; - @FXML private CheckBox alsoCompletedCheckbox; - @FXML private HBox projectFilterHbox; - private final Model model; - private final Controller controller; - - private final List> timeFilters = new ArrayList<>(); - private final List projectNameFilters = new ArrayList<>(); - + private FilteredList filteredWorkItems; @Autowired public FilterController(final Model model, final Controller controller) { @@ -56,8 +50,14 @@ public FilterController(final Model model, final Controller controller) { this.controller = controller; } + public FilteredList getFilteredWorkItems() { + return filteredWorkItems; + } + @FXML private void initialize() { + filteredWorkItems = new FilteredList<>(model.getWorkItems()); + model.getWorkItems().addListener((ListChangeListener) change -> { updateProjectFilterButtons(); }); @@ -124,9 +124,10 @@ private void updateProjectFilterButtons() { private void updateFilters() { Predicate filterPredicate = generateFilterPredicate(); - controller.setFilterPredicate(filterPredicate); + filteredWorkItems.setPredicate(filterPredicate); } + private Predicate generateFilterPredicate() { Predicate filterPredicate = (workItem) -> { boolean timeFilterMatches = timeFilters.isEmpty() ? true : timeFilters.stream().reduce(x -> false, Predicate::or).test(workItem); diff --git a/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java b/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java index fa58c49..76bbfef 100644 --- a/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java +++ b/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java @@ -22,10 +22,8 @@ import de.doubleslash.keeptask.model.Model; import de.doubleslash.keeptask.model.TodoPart; import de.doubleslash.keeptask.model.WorkItem; -import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; @@ -45,12 +43,8 @@ import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; @Component @@ -59,7 +53,8 @@ public class MainWindowController { private final Model model; private final Controller controller; - SortedList sortedWorkItems; + private FilterController filterController; + private SortingController sortingController; private Point dragDelta = new Point(0, 0); private Stage mainStage; @FXML @@ -82,10 +77,7 @@ public class MainWindowController { @FXML private Button addTodoButton; @FXML - private HBox sortingCriteriaHBox; - @FXML - private ComboBox addSortingCriteriaCbx; - private ObservableList sortingCriteriaList = FXCollections.observableArrayList(); + private VBox sortingVBox; // TODO extract TODO ListView to own controller @FXML private VBox workItemVBox; @@ -96,27 +88,10 @@ public MainWindowController(final Model model, final Controller controller) { this.controller = controller; } - public static Function orderBy(SortingCriteria criteria) { - switch (criteria) { - case Priority: - return WorkItem::getPriority; - case DueDate: - return WorkItem::getDueDateTime; - default: - throw new IllegalArgumentException("" + criteria); - } - } - @FXML private void initialize() { - // TODO make sorting configurable - sortedWorkItems = new SortedList<>(model.getWorkFilteredItems()); - Comparator comparing = Comparator.comparing(workItem -> workItem.getDueDateTime() != null ? workItem.getDueDateTime() : LocalDateTime.MIN); - comparing = comparing.reversed(); - sortedWorkItems.setComparator(comparing); - - loadFiltersLayout(); + loadSortingLayout(); closeButton.setOnAction(ae -> openConfirmationWindow()); minimizeButton.setOnAction(ae -> mainStage.setIconified(true)); @@ -150,36 +125,12 @@ private void initialize() { dueDatePicker.setValue(null); }); - model.getWorkFilteredItems().addListener((ListChangeListener) change -> { - refreshTodos(); - }); - - sortingCriteriaList.addListener((ListChangeListener) change -> { + model.getWorkItems().addListener((ListChangeListener) change -> { if (!change.next()) return; - Button sortingCriteriaButton = new Button(); - String sortingCriteria = change.getAddedSubList().get(0).toString(); - sortingCriteriaButton.setText(sortingCriteria); - Tooltip tooltip = new Tooltip(); - tooltip.setText("Click to remove " + sortingCriteria + " sorting criteria"); - sortingCriteriaButton.setTooltip(tooltip); - sortingCriteriaButton.setOnAction(event -> { - sortingCriteriaList.remove(SortingCriteria.valueOf(sortingCriteria)); - sortingCriteriaHBox.getChildren().remove(sortingCriteriaButton); - refreshTodos(); - }); - sortingCriteriaHBox.getChildren().add(sortingCriteriaHBox.getChildren().size() - 1, sortingCriteriaButton); refreshTodos(); }); - addSortingCriteriaCbx.setItems(FXCollections.observableArrayList(SortingCriteria.values())); - addSortingCriteriaCbx.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - if (newValue == null) return; - - sortingCriteriaList.add((SortingCriteria) newValue); - addSortingCriteriaCbx.getSelectionModel().clearSelection(); - }); - model.latestSelectedProjectProperty().addListener((obs, old, newValue) -> { projectTextInput.textProperty().set(newValue); }); @@ -187,10 +138,6 @@ private void initialize() { refreshTodos(); } - private void addSortingCriteria(String sortingCriteriaString) { - sortingCriteriaList.add(SortingCriteria.valueOf(sortingCriteriaString)); - } - private void loadFiltersLayout() { final FXMLLoader loader = FxmlLayout.createLoaderFor(Resources.RESOURCE.FXML_FILTER_LAYOUT); @@ -202,27 +149,36 @@ private void loadFiltersLayout() { throw new RuntimeException(e); } this.filterVBox.getChildren().addAll(filterVBox.getChildren()); // TODO losses original vbox attributes - FilterController filterController = loader.getController(); + filterController = loader.getController(); + } + + private void loadSortingLayout() { + final FXMLLoader loader = FxmlLayout.createLoaderFor(Resources.RESOURCE.FXML_SORTING_LAYOUT); + + final VBox sortingVBox; + try { + sortingVBox = loader.load(); + } catch (IOException e) { + LOG.error("Could not load sorting layout", e); + throw new RuntimeException(e); + } + this.sortingVBox.getChildren().addAll(sortingVBox.getChildren()); + sortingController = loader.getController(); + sortingController.setWorkItemsToSort(filterController.getFilteredWorkItems()); + sortingController.getSortedWorkItems().addListener((ListChangeListener) change -> { + if (!change.next()) return; + + refreshTodos(); + }); } private void refreshTodos() { ObservableList children = workItemVBox.getChildren(); children.clear(); - Stream sortedFilteredWorkItemStream = model.getWorkFilteredItems().stream(); - Comparator comparator = null; - if (sortingCriteriaList.size() > 0) { - comparator = Comparator.comparing(MainWindowController.orderBy(sortingCriteriaList.get(0))); - } - for (int i = 1; i < sortingCriteriaList.size(); i++) { - SortingCriteria sortingCriteria = sortingCriteriaList.get(i); - comparator = comparator.thenComparing(MainWindowController.orderBy(sortingCriteria)); - } + List sortedFilteredItems = sortingController.getSortedWorkItems(); - sortedFilteredWorkItemStream = comparator == null ? sortedFilteredWorkItemStream : sortedFilteredWorkItemStream.sorted(comparator); - List filteredItems = sortedFilteredWorkItemStream.collect(Collectors.toList()); - - for (WorkItem workItem : filteredItems) { + for (WorkItem workItem : sortedFilteredItems) { Node todoNode = createTodoNode(workItem); children.add(todoNode); } @@ -372,8 +328,4 @@ public void setMainStage(Stage mainStage) { this.mainStage = mainStage; mainStage.sizeToScene(); } - - private enum SortingCriteria { - Priority, DueDate; - } } diff --git a/src/main/java/de/doubleslash/keeptask/view/SortingController.java b/src/main/java/de/doubleslash/keeptask/view/SortingController.java new file mode 100644 index 0000000..3e2ee42 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptask/view/SortingController.java @@ -0,0 +1,101 @@ +package de.doubleslash.keeptask.view; + +import de.doubleslash.keeptask.model.WorkItem; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; +import org.springframework.stereotype.Component; + +import java.util.Comparator; +import java.util.function.Function; + +@Component +public class SortingController { + private final ObservableList sortingCriteriaList = FXCollections.observableArrayList(); + private SortedList sortedWorkItems; + @FXML + private HBox sortingCriteriaHBox; + @FXML + private ComboBox addSortingCriteriaCbx; + + private static Function orderBy(SortingCriteria criteria) { + switch (criteria) { + case Priority: + return WorkItem::getPriority; + case DueDate: + return WorkItem::getDueDateTime; + default: + throw new IllegalArgumentException("" + criteria); + } + } + + @FXML + private void initialize() { +// sortedWorkItems = new SortedList<>(FXCollections.observableArrayList()); + sortingCriteriaList.addListener((ListChangeListener) change -> { + if (!change.next()) return; + + updateComparator(); + + if (!change.wasAdded()) return; + + SortingCriteria addedSortingCriteria = change.getAddedSubList().get(0); + Button sortingCriteriaButton = new Button(); + sortingCriteriaButton.setText(addedSortingCriteria.toString()); + Tooltip tooltip = new Tooltip(); + tooltip.setText("Click to remove " + addedSortingCriteria + " sorting criteria"); + sortingCriteriaButton.setTooltip(tooltip); + sortingCriteriaButton.setOnAction(event -> { + sortingCriteriaList.remove(addedSortingCriteria); + addSortingCriteriaCbx.getItems().add(addedSortingCriteria); + sortingCriteriaHBox.getChildren().remove(sortingCriteriaButton); + }); + sortingCriteriaHBox.getChildren().add(sortingCriteriaHBox.getChildren().size() - 1, sortingCriteriaButton); + }); + + addSortingCriteriaCbx.setItems(FXCollections.observableArrayList(SortingCriteria.values())); + addSortingCriteriaCbx.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue == null) return; + + sortingCriteriaList.add((SortingCriteria) newValue); + addSortingCriteriaCbx.getSelectionModel().clearSelection(); + addSortingCriteriaCbx.getItems().remove(newValue); + }); + } + + private Comparator getSortingComparator() { + Comparator comparator = null; + if (sortingCriteriaList.size() > 0) { + comparator = Comparator.comparing(orderBy(sortingCriteriaList.get(0))); + } + for (int i = 1; i < sortingCriteriaList.size(); i++) { + SortingCriteria sortingCriteria = sortingCriteriaList.get(i); + comparator = comparator.thenComparing(orderBy(sortingCriteria)); + } + return comparator; + } + + private void updateComparator() { + Comparator comparator = getSortingComparator(); + sortedWorkItems.setComparator(comparator); + } + + public void setWorkItemsToSort(ObservableList workItemsToSort) { + sortedWorkItems = new SortedList<>(workItemsToSort); + updateComparator(); + } + + public SortedList getSortedWorkItems() { + return sortedWorkItems; + } + + private enum SortingCriteria { + Priority, DueDate; + } +} diff --git a/src/main/resources/layouts/MainWindowLayout.fxml b/src/main/resources/layouts/MainWindowLayout.fxml new file mode 100644 index 0000000..d567a43 --- /dev/null +++ b/src/main/resources/layouts/MainWindowLayout.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + +