Skip to content

Commit

Permalink
Add HistoryManager for record management and update SearchField with …
Browse files Browse the repository at this point in the history
…history support

- Introduce HistoryManager class to facilitate the management of historical records.
- Implement history management functionality in SearchField to enhance user interaction.
- Update SearchTextField with improvements for consistency and performance.
  • Loading branch information
leewyatt committed May 17, 2024
1 parent 16ec981 commit 5520446
Show file tree
Hide file tree
Showing 17 changed files with 1,817 additions and 503 deletions.
261 changes: 261 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,261 @@
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();
StringHistoryManager historyManager = new StringHistoryManager();
// historyManager.setPreferences(Preferences.userNodeForPackage(HistoryManagerApp.class).node("simpleTextField"));

HistoryButton<String> historyButton = new HistoryButton<>(textField, 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, 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, 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
Expand Up @@ -18,6 +18,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;

public class SearchFieldApp extends Application {
Expand Down Expand Up @@ -122,6 +123,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 ...");
// If not setPreferences() history records are only stored temporarily in memory and are not persisted locally.
getHistoryManager().setPreferences(Preferences.userNodeForPackage(SearchFieldApp.class).node("field"));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dlsc.gemsfx.demo;

import com.dlsc.gemsfx.SearchTextField;
import com.dlsc.gemsfx.util.StringHistoryManager;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
Expand All @@ -21,20 +22,21 @@

public class SearchTextFieldApp extends Application {

private SearchTextField field1;

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

field1 = new SearchTextField();
field1.setPreferences(Preferences.userNodeForPackage(SearchTextFieldApp.class).node("field1"));
SearchTextField field1 = new SearchTextField();
StringHistoryManager historyManager1 = field1.getHistoryManager();
historyManager1.setPreferences(Preferences.userNodeForPackage(SearchTextFieldApp.class).node("field1"));

SearchTextField field2 = new SearchTextField(true);
field2.setPreferences(Preferences.userNodeForPackage(SearchTextFieldApp.class).node("field2"));
StringHistoryManager historyManager2 = field2.getHistoryManager();
historyManager2.setPreferences(Preferences.userNodeForPackage(SearchTextFieldApp.class).node("field2"));

Label label = new Label("Max History Size:");
Spinner<Integer> maxHistorySizeSpinner = new Spinner<>(5, 50, 10, 5);
field1.maxHistorySizeProperty().bind(maxHistorySizeSpinner.valueProperty());
historyManager1.maxHistorySizeProperty().bind(maxHistorySizeSpinner.valueProperty());
historyManager2.maxHistorySizeProperty().bind(maxHistorySizeSpinner.valueProperty());
maxHistorySizeSpinner.setMaxWidth(Double.MAX_VALUE);
HBox maxHistorySizeBox = new HBox(5, label, maxHistorySizeSpinner);
maxHistorySizeBox.setAlignment(Pos.CENTER_LEFT);
Expand All @@ -58,25 +60,25 @@ public void start(Stage primaryStage) throws Exception {
setHistoryButton.setMaxWidth(Double.MAX_VALUE);
setHistoryButton.setOnAction(e -> {
List<String> list = List.of("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten");
field1.setHistory(list);
field2.setHistory(list);
historyManager1.set(list);
historyManager2.set(list);
});

Button addHistoryButton = new Button("Add History");
addHistoryButton.setMaxWidth(Double.MAX_VALUE);
addHistoryButton.setOnAction(e -> {
field1.addHistory("New " + LocalTime.now());
field2.addHistory("New" + LocalTime.now());
historyManager1.add("New " + LocalTime.now());
historyManager2.add("New" + LocalTime.now());
});

Button removeStandardHistoryButton = createRemoveHistoryButton("Standard Field Remove First History Item", field1);
Button removeRoundHistoryButton = createRemoveHistoryButton("Round Field Remove First History Item", field2);
Button removeStandardHistoryButton = createRemoveHistoryButton("Standard Field Remove First History Item", historyManager1);
Button removeRoundHistoryButton = createRemoveHistoryButton("Round Field Remove First History Item", historyManager2);

Button clearButton = new Button("Clear History");
clearButton.setMaxWidth(Double.MAX_VALUE);
clearButton.setOnAction(e -> {
field1.clearHistory();
field2.clearHistory();
historyManager1.clear();
historyManager2.clear();
});

VBox vbox = new VBox(20, new Label("Standard"), field1, new Label("Round"), field2,
Expand All @@ -92,11 +94,11 @@ public void start(Stage primaryStage) throws Exception {
primaryStage.show();
}

private Button createRemoveHistoryButton(String text, SearchTextField field) {
private Button createRemoveHistoryButton(String text, StringHistoryManager historyManager) {
Button removeHistoryButton2 = new Button(text);
removeHistoryButton2.disableProperty().bind(Bindings.createObjectBinding(() -> field.getUnmodifiableHistory().isEmpty(), field.getUnmodifiableHistory()));
removeHistoryButton2.disableProperty().bind(Bindings.createObjectBinding(() -> historyManager.getAll().isEmpty(), historyManager.getAll()));
removeHistoryButton2.setMaxWidth(Double.MAX_VALUE);
removeHistoryButton2.setOnAction(e -> field.removeHistory(field.getUnmodifiableHistory().get(0)));
removeHistoryButton2.setOnAction(e -> historyManager.remove(historyManager.getAll().get(0)));
return removeHistoryButton2;
}

Expand Down
Loading

0 comments on commit 5520446

Please sign in to comment.