From 5f9490d780bf353e9a3a1897a7491a01b26294cc Mon Sep 17 00:00:00 2001 From: yushijinhun Date: Wed, 3 Oct 2018 16:34:55 +0800 Subject: [PATCH] Add SafeStringConverter & Refactor font settings --- .../java/org/jackhuang/hmcl/ui/LogWindow.java | 3 +- .../org/jackhuang/hmcl/ui/SettingsPage.java | 29 ++-- .../hmcl/ui/construct/FontComboBox.java | 28 ++-- .../hmcl/ui/construct/Validator.java | 25 ++++ .../hmcl/util/javafx/SafeStringConverter.java | 124 ++++++++++++++++++ 5 files changed, 179 insertions(+), 30 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SafeStringConverter.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java index 42b9cf3d1..0f4d9b065 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java @@ -33,7 +33,6 @@ import javafx.stage.Stage; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.event.EventManager; import org.jackhuang.hmcl.game.LauncherHelper; -import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Log4jLevel; import org.jackhuang.hmcl.util.StringUtils; @@ -176,7 +175,7 @@ public final class LogWindow extends Stage { engine = webView.getEngine(); engine.loadContent(Lang.ignoringException(() -> IOUtils.readFullyAsString(getClass().getResourceAsStream("/assets/log-window-content.html"))) - .replace("${FONT}", Settings.instance().getFont().getSize() + "px \"" + Settings.instance().getFont().getFamily() + "\"")); + .replace("${FONT}", config().getFontSize() + "px \"" + config().getFontFamily() + "\"")); engine.getLoadWorker().stateProperty().addListener((a, b, newValue) -> { if (newValue == Worker.State.SUCCEEDED) { document = engine.getDocument(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java index 04467924e..252a7a246 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java @@ -36,8 +36,8 @@ import org.jackhuang.hmcl.upgrade.RemoteVersion; import org.jackhuang.hmcl.upgrade.UpdateChannel; import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.upgrade.UpdateHandler; -import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.i18n.Locales; +import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import java.net.Proxy; import java.util.Arrays; @@ -64,24 +64,19 @@ public final class SettingsPage extends SettingsView implements DecoratorPage { selectedItemPropertyFor(cboDownloadSource).bindBidirectional(config().downloadTypeProperty()); // ==== - cboFont.initValue(Settings.instance().getFont()); - cboFont.valueProperty().addListener((a, b, newValue) -> { - Font font = Font.font(newValue, Settings.instance().getFont().getSize()); - Settings.instance().setFont(font); - lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";"); - }); + // ==== Font ==== + cboFont.valueProperty().bindBidirectional(config().fontFamilyProperty()); - txtFontSize.setText(Double.toString(Settings.instance().getFont().getSize())); - txtFontSize.getValidators().add(new Validator(it -> Lang.toDoubleOrNull(it) != null)); - txtFontSize.textProperty().addListener((a, b, newValue) -> { - if (txtFontSize.validate()) { - Font font = Font.font(Settings.instance().getFont().getFamily(), Double.parseDouble(newValue)); - Settings.instance().setFont(font); - lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";"); - } - }); + txtFontSize.textProperty().bindBidirectional(config().fontSizeProperty(), + SafeStringConverter.fromFiniteDouble() + .restrict(it -> it > 0) + .fallbackTo(12.0) + .asPredicate(Validator.addTo(txtFontSize))); - lblDisplay.setStyle("-fx-font: " + Settings.instance().getFont().getSize() + " \"" + Settings.instance().getFont().getFamily() + "\";"); + lblDisplay.fontProperty().bind(Bindings.createObjectBinding( + () -> Font.font(config().getFontFamily(), config().getFontSize()), + config().fontFamilyProperty(), config().fontSizeProperty())); + // ==== // ==== Languages ==== cboLanguage.getItems().setAll(Locales.LOCALES); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java index 2708b3f5a..6b7bfe155 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java @@ -17,20 +17,26 @@ */ package org.jackhuang.hmcl.ui.construct; +import static javafx.collections.FXCollections.emptyObservableList; +import static javafx.collections.FXCollections.observableList; +import static javafx.collections.FXCollections.singletonObservableList; + +import org.jackhuang.hmcl.util.javafx.MultiStepBinding; + import com.jfoenix.controls.JFXComboBox; + import javafx.beans.NamedArg; +import javafx.beans.binding.Bindings; import javafx.scene.control.ListCell; import javafx.scene.text.Font; public class FontComboBox extends JFXComboBox { + private boolean loaded = false; public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize, @NamedArg(value = "enableStyle", defaultValue = "false") boolean enableStyle) { - valueProperty().addListener((a, b, newValue) -> { - if (enableStyle) - setStyle("-fx-font-family: \"" + newValue + "\";"); - }); + styleProperty().bind(Bindings.concat("-fx-font-family: \"", valueProperty(), "\"")); setCellFactory(listView -> new ListCell() { @Override @@ -43,15 +49,15 @@ public class FontComboBox extends JFXComboBox { } }); + itemsProperty().bind(MultiStepBinding.of(valueProperty()) + .map(value -> value == null ? emptyObservableList() : singletonObservableList(value))); + setOnMouseClicked(e -> { - if (loaded) return; - getItems().setAll(Font.getFamilies()); + if (loaded) + return; + itemsProperty().unbind(); + setItems(observableList(Font.getFamilies())); loaded = true; }); } - - public void initValue(Font font) { - getItems().setAll(font.getFamily()); - getSelectionModel().select(font.getFamily()); - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Validator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Validator.java index b06b5cb49..d0a878f21 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Validator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Validator.java @@ -17,12 +17,37 @@ */ package org.jackhuang.hmcl.ui.construct; +import com.jfoenix.controls.JFXTextField; import com.jfoenix.validation.base.ValidatorBase; + +import javafx.beans.InvalidationListener; +import javafx.beans.WeakInvalidationListener; import javafx.scene.control.TextInputControl; +import java.util.function.Consumer; import java.util.function.Predicate; +import org.jackhuang.hmcl.util.javafx.SafeStringConverter; + public final class Validator extends ValidatorBase { + + public static Consumer> addTo(JFXTextField control) { + return addTo(control, null); + } + + /** + * @see SafeStringConverter#asPredicate(Consumer) + */ + public static Consumer> addTo(JFXTextField control, String message) { + return predicate -> { + Validator validator = new Validator(message, predicate); + InvalidationListener listener = any -> control.validate(); + validator.getProperties().put(validator, listener); + control.textProperty().addListener(new WeakInvalidationListener(listener)); + control.getValidators().add(validator); + }; + } + private final Predicate validator; /** diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SafeStringConverter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SafeStringConverter.java new file mode 100644 index 000000000..c52718ef9 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SafeStringConverter.java @@ -0,0 +1,124 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2018 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util.javafx; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import org.jackhuang.hmcl.util.function.ExceptionalFunction; + +import javafx.util.StringConverter; + +/** + * @author yushijinhun + */ +public class SafeStringConverter extends StringConverter { + + public static SafeStringConverter fromInteger() { + return new SafeStringConverter(Integer::parseInt, NumberFormatException.class) + .fallbackTo(0); + } + + public static SafeStringConverter fromDouble() { + return new SafeStringConverter(Double::parseDouble, NumberFormatException.class) + .fallbackTo(0.0); + } + + public static SafeStringConverter fromFiniteDouble() { + return new SafeStringConverter(Double::parseDouble, NumberFormatException.class) + .restrict(Double::isFinite) + .fallbackTo(0.0); + } + + private ExceptionalFunction converter; + private Class malformedExceptionClass; + private S fallbackValue = null; + private List> restrictions = new ArrayList<>(); + + public SafeStringConverter(ExceptionalFunction converter, Class malformedExceptionClass) { + this.converter = converter; + this.malformedExceptionClass = malformedExceptionClass; + } + + @Override + public String toString(T object) { + return object == null ? "" : object.toString(); + } + + @Override + public S fromString(String string) { + return tryParse(string).orElse(fallbackValue); + } + + private Optional tryParse(String string) { + if (string == null) { + return Optional.empty(); + } + + S converted; + try { + converted = converter.apply(string); + } catch (Exception e) { + if (malformedExceptionClass.isInstance(e)) { + return Optional.empty(); + } + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new RuntimeException(e); + } + } + + if (!filter(converted)) { + return Optional.empty(); + } + + return Optional.of(converted); + } + + protected boolean filter(S value) { + for (Predicate restriction : restrictions) { + if (!restriction.test(value)) { + return false; + } + } + return true; + } + + public SafeStringConverter fallbackTo(S fallbackValue) { + this.fallbackValue = fallbackValue; + return this; + } + + public SafeStringConverter restrict(Predicate condition) { + this.restrictions.add(condition); + return this; + } + + public Predicate asPredicate() { + return string -> tryParse(string).isPresent(); + } + + public SafeStringConverter asPredicate(Consumer> consumer) { + consumer.accept(asPredicate()); + return this; + } +}