From a94bb407e55a32fff7821acbb031eba73e80e09c Mon Sep 17 00:00:00 2001 From: Glavo Date: Fri, 25 Nov 2022 17:20:16 +0800 Subject: [PATCH] Submit the modification of JFXTextField after editing (#1869) --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 80 +++++++++++++++++-- .../hmcl/ui/main/DownloadSettingsPage.java | 15 ++-- .../hmcl/ui/main/PersonalizationPage.java | 10 +-- .../hmcl/ui/versions/VersionSettingsPage.java | 21 +++-- 4 files changed, 94 insertions(+), 32 deletions(-) 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 498cbdcd7..f5be906a6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -72,6 +72,7 @@ import javax.xml.parsers.ParserConfigurationException; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; +import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URI; @@ -530,20 +531,85 @@ public final class FXUtils { } } - public static void bindInt(JFXTextField textField, Property property) { - textField.textProperty().bindBidirectional(property, SafeStringConverter.fromInteger()); + 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); } - public static void unbindInt(JFXTextField textField, Property property) { - textField.textProperty().unbindBidirectional(property); + public static void bindInt(JFXTextField textField, Property property) { + bind(textField, property, SafeStringConverter.fromInteger()); } public static void bindString(JFXTextField textField, Property property) { - textField.textProperty().bindBidirectional(property); + bind(textField, property, null); } - public static void unbindString(JFXTextField textField, Property property) { - textField.textProperty().unbindBidirectional(property); + public static void unbind(JFXTextField textField, Property property) { + TextFieldBindingListener listener = new TextFieldBindingListener<>(textField, property, null); + textField.focusedProperty().removeListener((ChangeListener) listener); + property.removeListener(listener); + } + + private static final class TextFieldBindingListener implements ChangeListener, InvalidationListener { + private final int hashCode; + private final WeakReference textFieldRef; + private final WeakReference> propertyRef; + private final StringConverter converter; + + TextFieldBindingListener(JFXTextField textField, Property property, StringConverter converter) { + this.textFieldRef = new WeakReference<>(textField); + this.propertyRef = new WeakReference<>(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); + } + } + } + + @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 static void bindBoolean(JFXToggleButton toggleButton, Property property) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java index 2d246750e..eddddf2f6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java @@ -132,7 +132,7 @@ public class DownloadSettingsPage extends StackPane { JFXTextField threadsField = new JFXTextField(); FXUtils.setLimitWidth(threadsField, 60); - threadsField.textProperty().bindBidirectional(config().downloadThreadsProperty(), SafeStringConverter.fromInteger()); + FXUtils.bindInt(threadsField, config().downloadThreadsProperty()); AtomicBoolean changedByTextField = new AtomicBoolean(false); FXUtils.onChangeAndOperate(config().downloadThreadsProperty(), value -> { @@ -213,7 +213,7 @@ public class DownloadSettingsPage extends StackPane { GridPane.setRowIndex(txtProxyHost, 1); GridPane.setColumnIndex(txtProxyHost, 1); gridPane.getChildren().add(txtProxyHost); - txtProxyHost.textProperty().bindBidirectional(config().proxyHostProperty()); + FXUtils.bindString(txtProxyHost, config().proxyHostProperty()); txtProxyHost.getValidators().setAll(new NumberValidator(i18n("input.number"), false)); } @@ -232,11 +232,10 @@ public class DownloadSettingsPage extends StackPane { FXUtils.setValidateWhileTextChanged(txtProxyPort, true); gridPane.getChildren().add(txtProxyPort); - txtProxyPort.textProperty().bindBidirectional(config().proxyPortProperty(), - SafeStringConverter.fromInteger() - .restrict(it -> it >= 0 && it <= 0xFFFF) - .fallbackTo(0) - .asPredicate(Validator.addTo(txtProxyPort))); + FXUtils.bind(txtProxyPort, config().proxyPortProperty(), SafeStringConverter.fromInteger() + .restrict(it -> it >= 0 && it <= 0xFFFF) + .fallbackTo(0) + .asPredicate(Validator.addTo(txtProxyPort))); } proxyPane.getChildren().add(gridPane); } @@ -273,7 +272,7 @@ public class DownloadSettingsPage extends StackPane { GridPane.setRowIndex(txtProxyUsername, 0); GridPane.setColumnIndex(txtProxyUsername, 1); authPane.getChildren().add(txtProxyUsername); - txtProxyUsername.textProperty().bindBidirectional(config().proxyUserProperty()); + FXUtils.bindString(txtProxyUsername, config().proxyUserProperty()); } { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java index 8544953e0..5d8b2ee54 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java @@ -146,12 +146,10 @@ public class PersonalizationPage extends StackPane { JFXTextField txtLogFontSize = new JFXTextField(); FXUtils.setLimitWidth(txtLogFontSize, 50); - txtLogFontSize.textProperty().bindBidirectional(config().fontSizeProperty(), - SafeStringConverter.fromFiniteDouble() - .restrict(it -> it > 0) - .fallbackTo(12.0) - .asPredicate(Validator.addTo(txtLogFontSize))); - + FXUtils.bind(txtLogFontSize, config().fontSizeProperty(), SafeStringConverter.fromFiniteDouble() + .restrict(it -> it > 0) + .fallbackTo(12.0) + .asPredicate(Validator.addTo(txtLogFontSize))); hBox.getChildren().setAll(cboLogFont, txtLogFontSize); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index e662c1264..aca9e1c24 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -43,7 +43,6 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.javafx.BindingMapping; -import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -259,7 +258,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag JFXTextField txtMaxMemory = new JFXTextField(); FXUtils.setLimitWidth(txtMaxMemory, 60); FXUtils.setValidateWhileTextChanged(txtMaxMemory, true); - txtMaxMemory.textProperty().bindBidirectional(maxMemory, SafeStringConverter.fromInteger()); + FXUtils.bindInt(txtMaxMemory, maxMemory); txtMaxMemory.setValidators(new NumberValidator(i18n("input.number"), false)); lowerBoundPane.getChildren().setAll(label, slider, txtMaxMemory, new Label("MB")); @@ -628,19 +627,19 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag // unbind data fields if (lastVersionSetting != null) { - FXUtils.unbindInt(txtWidth, lastVersionSetting.widthProperty()); - FXUtils.unbindInt(txtHeight, lastVersionSetting.heightProperty()); + FXUtils.unbind(txtWidth, lastVersionSetting.widthProperty()); + FXUtils.unbind(txtHeight, lastVersionSetting.heightProperty()); maxMemory.unbindBidirectional(lastVersionSetting.maxMemoryProperty()); javaCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.javaDirProperty()); gameDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.gameDirProperty()); nativesDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.nativesDirProperty()); - FXUtils.unbindString(txtJVMArgs, lastVersionSetting.javaArgsProperty()); - FXUtils.unbindString(txtGameArgs, lastVersionSetting.minecraftArgsProperty()); - FXUtils.unbindString(txtMetaspace, lastVersionSetting.permSizeProperty()); - FXUtils.unbindString(txtWrapper, lastVersionSetting.wrapperProperty()); - FXUtils.unbindString(txtPreLaunchCommand, lastVersionSetting.preLaunchCommandProperty()); - FXUtils.unbindString(txtPostExitCommand, lastVersionSetting.postExitCommandProperty()); - FXUtils.unbindString(txtServerIP, lastVersionSetting.serverIpProperty()); + FXUtils.unbind(txtJVMArgs, lastVersionSetting.javaArgsProperty()); + FXUtils.unbind(txtGameArgs, lastVersionSetting.minecraftArgsProperty()); + FXUtils.unbind(txtMetaspace, lastVersionSetting.permSizeProperty()); + FXUtils.unbind(txtWrapper, lastVersionSetting.wrapperProperty()); + FXUtils.unbind(txtPreLaunchCommand, lastVersionSetting.preLaunchCommandProperty()); + FXUtils.unbind(txtPostExitCommand, lastVersionSetting.postExitCommandProperty()); + FXUtils.unbind(txtServerIP, lastVersionSetting.serverIpProperty()); FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty()); FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty()); noGameCheckPane.selectedProperty().unbindBidirectional(lastVersionSetting.notCheckGameProperty());