Skip to content

Commit

Permalink
Merge pull request #178 from dlsc-software-consulting-gmbh/feature-hi…
Browse files Browse the repository at this point in the history
…story-management-enhancements

Add HistoryManager for record management and update SearchField with history support
  • Loading branch information
dlemmermann authored May 30, 2024
2 parents bc7a047 + 239019e commit 5f940ac
Show file tree
Hide file tree
Showing 18 changed files with 2,074 additions and 567 deletions.
274 changes: 274 additions & 0 deletions gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/HistoryManagerApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
package com.dlsc.gemsfx.demo;

import com.dlsc.gemsfx.HistoryButton;
import com.dlsc.gemsfx.Spacer;
import com.dlsc.gemsfx.util.HistoryManager;
import com.dlsc.gemsfx.util.PreferencesHistoryManager;
import com.dlsc.gemsfx.util.StringHistoryManager;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.materialdesign.MaterialDesign;

import java.util.List;
import java.util.Objects;
import java.util.prefs.Preferences;

/**
* A demo application that shows how to use {@link HistoryButton} and {@link HistoryManager}.
*/
public class HistoryManagerApp extends Application {

@Override
public void start(Stage primaryStage) throws Exception {

TabPane tabPane = new TabPane();
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
tabPane.getTabs().addAll(
new Tab("Basic", basicDemo()),
new Tab("Advanced", advancedDemo()),
new Tab("Other", otherDemo())
);

primaryStage.setScene(new Scene(tabPane, 800, 600));
primaryStage.setTitle("History Manager Demo");
primaryStage.show();
}

private Node basicDemo() {
TextField textField = new TextField();

HistoryButton<String> historyButton = new HistoryButton<>(textField);

// Tips: We can set the delimiter and preferencesKey when creating, otherwise use the default value.
// StringHistoryManager historyManager = new StringHistoryManager(";", "history-records",
// Preferences.userNodeForPackage(HistoryManagerApp.class).node("simpleTextField"));

StringHistoryManager historyManager = new StringHistoryManager();

// Tips: If we want to persist the history after the application restarts, we need to set the preferences.
// historyManager.setPreferences(Preferences.userNodeForPackage(HistoryManagerApp.class).node("simpleTextField"));

// Tips: If we want to enable the history function, we need to set the history manager.
historyButton.setHistoryManager(historyManager);

historyButton.setConfigureHistoryPopup(historyPopup -> {
// When choosing a history item, replace the text in the text field.
historyPopup.setOnHistoryItemConfirmed(item -> {
if (item != null) {
textField.setText(item);
}
historyPopup.hide();
});
});

// Add history item to the history when the enter key is pressed.
textField.setOnKeyPressed(e -> {
historyButton.hideHistoryPopup();
if (e.getCode() == KeyCode.ENTER) {
historyManager.add(textField.getText());
}
});

HBox box = new HBox(5, textField, historyButton);
box.setAlignment(Pos.CENTER);
Label label = new Label("""
1. Tips: Press Enter to add the text to the history.
2. Click the history button to show the history popup.
3. This is a simple case, since the preferencesKey is not set, it will not be persisted, just saved in memory.
""");
label.setStyle("-fx-text-fill: #666;");

VBox vbox = new VBox(50, label, box);
vbox.setAlignment(Pos.CENTER);
return vbox;
}

/**
* Creates a text field with a history button.
*/
private Node advancedDemo() {
TextField textField = new TextField();

StringHistoryManager historyManager = new StringHistoryManager();
// Tips: You can set the delimiter and preferencesKey when creating, otherwise use the default value.
// PreferencesHistoryManager historyManager = new PreferencesHistoryManager(";", "save-items");

// Tips: If you want to persist the history after the application restarts, Please set the preferences.
historyManager.setPreferences(Preferences.userNodeForPackage(HistoryManagerApp.class).node("textField"));
// Optional: Set the maximum history size.default is 30.
historyManager.setMaxHistorySize(10);
// Optional: if the history is empty, set some default values
if (historyManager.getAll().isEmpty()) {
historyManager.set(List.of("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"));
}

HistoryButton<String> historyButton = new HistoryButton<>(textField);
historyButton.setHistoryManager(historyManager);

// add history item to the history when the enter key is pressed.
textField.setOnKeyPressed(e -> {
historyButton.hideHistoryPopup();
if (e.getCode() == KeyCode.ENTER) {
historyManager.add(textField.getText());
}
});

// Optional: true means the popup owner will be focused when the history popup is opened.
// historyButton.setFocusPopupOwnerOnOpen(true);

// Optional: Configure the history popup
historyButton.setConfigureHistoryPopup(historyPopup -> {

historyPopup.setHistoryPlaceholder(new Label("Tips: No history items available."));

historyPopup.setOnHistoryItemConfirmed(item -> {
if (item != null) {
int length = textField.textProperty().getValueSafe().length();
textField.replaceText(0, length, item);
}
historyPopup.hide();
});

// create the left node;
VBox leftBox = new VBox();
Label label = new Label("History");
label.setRotate(90);
Group group = new Group(label);

Button clearAll = new Button("", new FontIcon(MaterialDesign.MDI_DELETE));
clearAll.setPadding(new Insets(2, 4, 2, 4));
clearAll.setOnAction(e -> {
historyManager.clear();
historyPopup.hide();
});
clearAll.managedProperty().bind(clearAll.visibleProperty());
clearAll.visibleProperty().bind(Bindings.isNotEmpty(historyManager.getAll()));

leftBox.getChildren().addAll(group, new Spacer(), clearAll);
leftBox.setAlignment(Pos.CENTER);
leftBox.setPadding(new Insets(10, 5, 5, 5));
leftBox.setPrefWidth(35);
leftBox.setStyle("-fx-background-color: #f4f4f4;");
historyPopup.setLeft(leftBox);
}
);


HBox box = new HBox(5, textField, historyButton);
box.setAlignment(Pos.CENTER);

Label label = new Label("""
1. Tips: Press Enter to add the text to the history.
2. Click the history button to show the history popup.
""");
label.setStyle("-fx-text-fill: #666;");

VBox vbox = new VBox(50, label, box);
vbox.setAlignment(Pos.CENTER);
return vbox;
}

/**
* Creates a list view with a history button.
*/
private Node otherDemo() {
ListView<Student> listView = new ListView<>();
listView.getItems().addAll(
new Student("John", 90),
new Student("Doe", 95),
new Student("Jane", 85),
new Student("Smith", 92),
new Student("Alice", 72),
new Student("Bob", 68),
new Student("Eve", 91),
new Student("Mallory", 66),
new Student("Charlie", 79),
new Student("David", 83)
);

PreferencesHistoryManager<Student> historyManager = new PreferencesHistoryManager<>(
new StringConverter<>() {
@Override
public String toString(Student object) {
return object.name() + " : " + object.score();
}

@Override
public Student fromString(String string) {
String[] parts = string.split(" : ");
return new Student(parts[0], Integer.parseInt(parts[1]));
}
}
);

listView.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
historyManager.add(listView.getSelectionModel().getSelectedItem());
}
});

historyManager.setPreferences(Preferences.userNodeForPackage(HistoryManagerApp.class).node("list"));

HistoryButton<Student> historyButton = new HistoryButton<>(null);
historyButton.setHistoryManager(historyManager);
historyButton.setText("History");

historyButton.setConfigureHistoryPopup(historyPopup -> {
historyPopup.setOnHistoryItemConfirmed(item -> {
if (item != null) {
listView.getSelectionModel().select(item);
}
historyPopup.hide();
});
});

Label label = new Label("""
1. Tips: Double-click the item to add it to the history.
2. Click the history button to show the history popup.
3. Click the item in the history popup to select it in the list view.
""");
label.setStyle("-fx-text-fill: #666;");

VBox vBox = new VBox(15, label, listView, historyButton);
vBox.setAlignment(Pos.CENTER);
vBox.setPadding(new Insets(30));
return vBox;
}

public record Student(String name, int score) {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return score == student.score && Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(name, score);
}
}

public static void main(String[] args) {
launch(args);
}

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.dlsc.gemsfx.demo;

import com.dlsc.gemsfx.SearchField;
import com.dlsc.gemsfx.util.HistoryManager;
import com.dlsc.gemsfx.util.StringHistoryManager;
import fr.brouillard.oss.cssfx.CSSFX;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
Expand All @@ -18,10 +21,18 @@
import java.util.Comparator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;

/**
* This demo shows how to use the {@link SearchField} control.
* <p>
* About the HistoryManager, you can refer to: {@link HistoryManager} {@link SearchTextFieldApp}, {@link HistoryManagerApp}
*/
public class SearchFieldApp extends Application {

private StringHistoryManager historyManager;

private final List<Country> countries = new ArrayList<>();

@Override
Expand Down Expand Up @@ -80,10 +91,45 @@ public void start(Stage primaryStage) throws Exception {
CheckBox autoCommitOnFocusLostBox = new CheckBox("Auto commit on field lost focus.");
autoCommitOnFocusLostBox.selectedProperty().bindBidirectional(field.autoCommitOnFocusLostProperty());

CheckBox enableHistoryBox = new CheckBox("Enable History");
enableHistoryBox.selectedProperty().addListener((obs, oldVal, newVal) -> {
if (newVal) {
if (field.getHistoryManager() == null) {
historyManager = new StringHistoryManager(Preferences.userNodeForPackage(SearchFieldApp.class).node("field"));
// Optional: Set the maximum history size. default is 30.
historyManager.setMaxHistorySize(20);
// Optional: If the history items is empty, we can set a default history list.
if (historyManager.getAll().isEmpty()) {
historyManager.set(List.of("United Kingdom", "Switzerland"));
}
}
// If the history manager is not null, the search field will have a history feature.
field.setHistoryManager(historyManager);
} else {
// If the history manager is null, the search field will not have a history feature.
field.setHistoryManager(null);
}
primaryStage.sizeToScene();
});
enableHistoryBox.setSelected(true);

CheckBox addHistoryOnActionBox = new CheckBox("Add History on Enter");
addHistoryOnActionBox.setSelected(true);
field.addingItemToHistoryOnEnterProperty().bind(addHistoryOnActionBox.selectedProperty());

CheckBox addHistoryOnFocusLossBox = new CheckBox("Add History on Focus Loss");
addHistoryOnFocusLossBox.setSelected(true);
field.addingItemToHistoryOnFocusLostProperty().bind(addHistoryOnFocusLossBox.selectedProperty());

VBox historyControls = new VBox(10, new Separator(), addHistoryOnActionBox, addHistoryOnFocusLossBox);
historyControls.managedProperty().bind(enableHistoryBox.selectedProperty());
historyControls.visibleProperty().bind(enableHistoryBox.selectedProperty());

field.leftProperty().bind(Bindings.createObjectBinding(() -> showLeftRightNodes.isSelected() ? regionLeft : null, showLeftRightNodes.selectedProperty()));
field.rightProperty().bind(Bindings.createObjectBinding(() -> showLeftRightNodes.isSelected() ? regionRight : null, showLeftRightNodes.selectedProperty()));

VBox vbox = new VBox(20, createNewItemBox, showPromptText, usePlaceholder, hideWithSingleChoiceBox, hideWithNoChoiceBox, showSearchIconBox, showLeftRightNodes, autoCommitOnFocusLostBox, hBox, hBox2, field);
VBox vbox = new VBox(20, createNewItemBox, showPromptText, usePlaceholder, hideWithSingleChoiceBox, hideWithNoChoiceBox, showSearchIconBox, showLeftRightNodes,
autoCommitOnFocusLostBox, hBox, hBox2, enableHistoryBox, historyControls, field);
vbox.setPadding(new Insets(20));

Scene scene = new Scene(vbox);
Expand Down Expand Up @@ -122,6 +168,8 @@ public Country fromString(String string) {
setMatcher((broker, searchText) -> broker.getName().toLowerCase().startsWith(searchText.toLowerCase()));
setComparator(Comparator.comparing(Country::getName));
getEditor().setPromptText("Start typing country name ...");
// Tips: If we don't set a HistoryManager, the search field will not have a history feature.
// setHistoryManager(new StringHistoryManager(Preferences.userNodeForPackage(SearchFieldApp.class).node("field")));
}
}

Expand Down
Loading

0 comments on commit 5f940ac

Please sign in to comment.