From 81b3911dc901eb1bcd34f6a29a18057459341270 Mon Sep 17 00:00:00 2001 From: Haowei Wen Date: Mon, 30 Dec 2024 00:02:32 +0800 Subject: [PATCH] Fix value in text field might not be saved when pressing ESC https://github.com/HMCL-dev/HMCL/pull/1869#issuecomment-2335027530 --- .../hmcl/game/HMCLGameRepository.java | 2 + .../java/org/jackhuang/hmcl/ui/FXUtils.java | 113 +++++++++--------- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 503f2870e..189313ca9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -337,6 +337,8 @@ public class HMCLGameRepository extends DefaultGameRepository { if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) return false; + LOG.info("Saving version setting: " + id); + try { FileUtils.writeText(file, GSON.toJson(localVersionSettings.get(id))); return true; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index d42a5913b..4b7087eb7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -31,6 +31,7 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Cursor; import javafx.scene.Node; +import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.*; @@ -521,10 +522,12 @@ public final class FXUtils { } public static void bind(JFXTextField textField, Property property, StringConverter converter) { - textField.setText(converter == null ? (String) property.getValue() : converter.toString(property.getValue())); - TextFieldBindingListener listener = new TextFieldBindingListener<>(textField, property, converter); - textField.focusedProperty().addListener((ChangeListener) listener); - property.addListener(listener); + TextFieldBinding binding = new TextFieldBinding<>(textField, property, converter); + binding.updateTextField(); + textField.getProperties().put("FXUtils.bind.binding", binding); + textField.focusedProperty().addListener(binding.focusedListener); + textField.sceneProperty().addListener(binding.sceneListener); + property.addListener(binding.propertyListener); } public static void bindInt(JFXTextField textField, Property property) { @@ -536,68 +539,66 @@ public final class FXUtils { } public static void unbind(JFXTextField textField, Property property) { - TextFieldBindingListener listener = new TextFieldBindingListener<>(textField, property, null); - textField.focusedProperty().removeListener((ChangeListener) listener); - property.removeListener(listener); + TextFieldBinding binding = (TextFieldBinding) textField.getProperties().remove("FXUtils.bind.binding"); + if (binding != null) { + textField.focusedProperty().removeListener(binding.focusedListener); + textField.sceneProperty().removeListener(binding.sceneListener); + property.removeListener(binding.propertyListener); + } } - private static final class TextFieldBindingListener implements ChangeListener, InvalidationListener { - private final int hashCode; - private final WeakReference textFieldRef; - private final WeakReference> propertyRef; + private static final class TextFieldBinding { + private final JFXTextField textField; + private final Property property; private final StringConverter converter; - TextFieldBindingListener(JFXTextField textField, Property property, StringConverter converter) { - this.textFieldRef = new WeakReference<>(textField); - this.propertyRef = new WeakReference<>(property); + public final ChangeListener focusedListener; + public final ChangeListener sceneListener; + public final InvalidationListener propertyListener; + + public TextFieldBinding(JFXTextField textField, Property property, StringConverter converter) { + this.textField = textField; + this.property = property; this.converter = converter; - this.hashCode = System.identityHashCode(textField) ^ System.identityHashCode(property); - } - @Override - public void changed(ObservableValue observable, Boolean oldValue, Boolean focused) { // On TextField changed - JFXTextField textField = textFieldRef.get(); - Property property = this.propertyRef.get(); - - if (textField != null && property != null && oldValue == Boolean.TRUE && focused == Boolean.FALSE) { - if (textField.validate()) { - String newText = textField.getText(); - @SuppressWarnings("unchecked") - T newValue = converter == null ? (T) newText : converter.fromString(newText); - - if (!Objects.equals(newValue, property.getValue())) - property.setValue(newValue); - } else { - // Rollback to old value - invalidated(null); + focusedListener = (observable, oldFocused, newFocused) -> { + if (oldFocused && !newFocused) { + if (textField.validate()) { + uppdateProperty(); + } else { + // Rollback to old value + updateTextField(); + } } + }; + + sceneListener = (observable, oldScene, newScene) -> { + if (oldScene != null && newScene == null) { + // Component is being removed from scene + if (textField.validate()) { + uppdateProperty(); + } + } + }; + + propertyListener = observable -> { + updateTextField(); + }; + } + + public void uppdateProperty() { + String newText = textField.getText(); + @SuppressWarnings("unchecked") + T newValue = converter == null ? (T) newText : converter.fromString(newText); + + if (!Objects.equals(newValue, property.getValue())) { + property.setValue(newValue); } } - @Override - public void invalidated(Observable observable) { // On property change - JFXTextField textField = textFieldRef.get(); - Property property = this.propertyRef.get(); - - if (textField != null && property != null) { - T value = property.getValue(); - textField.setText(converter == null ? (String) value : converter.toString(value)); - } - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof TextFieldBindingListener)) - return false; - TextFieldBindingListener other = (TextFieldBindingListener) obj; - return this.hashCode == other.hashCode - && this.textFieldRef.get() == other.textFieldRef.get() - && this.propertyRef.get() == other.propertyRef.get(); + public void updateTextField() { + T value = property.getValue(); + textField.setText(converter == null ? (String) value : converter.toString(value)); } }