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 c8af8a8a19..e024d474e1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -74,8 +74,6 @@ import java.awt.*; import java.io.*; import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -230,23 +228,22 @@ public static Node wrapMargin(Node node, Insets insets) { return new StackPane(node); } - public static void setValidateWhileTextChanged(Node field, boolean validate) { - if (field instanceof JFXTextField) { - if (validate) { - addListener(field, "FXUtils.validation", ((JFXTextField) field).textProperty(), o -> ((JFXTextField) field).validate()); - } else { - removeListener(field, "FXUtils.validation"); - } - ((JFXTextField) field).validate(); - } else if (field instanceof JFXPasswordField) { - if (validate) { - addListener(field, "FXUtils.validation", ((JFXPasswordField) field).textProperty(), o -> ((JFXPasswordField) field).validate()); - } else { - removeListener(field, "FXUtils.validation"); - } - ((JFXPasswordField) field).validate(); - } else - throw new IllegalArgumentException("Only JFXTextField and JFXPasswordField allowed"); + public static void setValidateWhileTextChanged(JFXTextField field, boolean validate) { + if (validate) { + addListener(field, "FXUtils.validation", field.textProperty(), o -> field.validate()); + } else { + removeListener(field, "FXUtils.validation"); + } + field.validate(); + } + + public static void setValidateWhileTextChanged(JFXPasswordField field, boolean validate) { + if (validate) { + addListener(field, "FXUtils.validation", field.textProperty(), o -> field.validate()); + } else { + removeListener(field, "FXUtils.validation"); + } + field.validate(); } public static boolean getValidateWhileTextChanged(Node field) { @@ -318,29 +315,7 @@ public static void installSlowTooltip(Node node, String tooltip) { } public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { - runInFX(() -> { - try { - // Java 8 - Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); - Constructor behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); - behaviorConstructor.setAccessible(true); - Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false); - Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); - installMethod.setAccessible(true); - installMethod.invoke(behavior, node, tooltip); - } catch (ReflectiveOperationException e) { - try { - // Java 9 - Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay)); - Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay)); - Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay)); - } catch (ReflectiveOperationException e2) { - e.addSuppressed(e2); - Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e); - } - Tooltip.install(node, tooltip); - } - }); + runInFX(() -> TooltipInstaller.install(node, openDelay, visibleDelay, closeDelay, tooltip)); } public static void playAnimation(Node node, String animationKey, Timeline timeline) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java new file mode 100644 index 0000000000..a9f782bdb3 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java @@ -0,0 +1,130 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * 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 . + */ +package org.jackhuang.hmcl.ui; + +import javafx.scene.Node; +import javafx.scene.control.Tooltip; +import javafx.util.Duration; +import org.jackhuang.hmcl.util.Logging; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.logging.Level; + +public abstract class TooltipInstaller { + // Fallback: Java8TooltipInstaller -> Java9TooltipInstaller -> NoDurationTooltipInstaller + + private static final class Java8TooltipInstaller extends TooltipInstaller { + public static TooltipInstaller of() { + try { + // Java 8 + Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); + Constructor behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); + behaviorConstructor.setAccessible(true); + Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); + installMethod.setAccessible(true); + return new Java8TooltipInstaller(behaviorConstructor, installMethod); + } catch (ReflectiveOperationException e) { + Logging.LOG.log(Level.WARNING, "Cannot use Java 8 Tooltip Installer, fallback to Java 9 Tooltip Installer.", e); + return Java9TooltipInstaller.of(); + } + } + + private final Constructor behaviorConstructor; + + private final Method installMethod; + + public Java8TooltipInstaller(Constructor behaviorConstructor, Method installMethod) { + this.behaviorConstructor = behaviorConstructor; + this.installMethod = installMethod; + } + + @Override + protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { + try { + installMethod.invoke( + behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false), + node, + tooltip + ); + + return this; + } catch (ReflectiveOperationException e) { + Logging.LOG.log(Level.WARNING, "Cannot use Java 8 Tooltip Installer, fallback to Java 9 Tooltip Installer.", e); + return Java9TooltipInstaller.of().installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip); + } + } + } + + private static final class Java9TooltipInstaller extends TooltipInstaller { + public static TooltipInstaller of() { + try { + Method setShowDelay = Tooltip.class.getMethod("setShowDelay", Duration.class); + Method setShowDuration = Tooltip.class.getMethod("setShowDuration", Duration.class); + Method setHideDelay = Tooltip.class.getMethod("setHideDelay", Duration.class); + return new Java9TooltipInstaller(setShowDelay, setShowDuration, setHideDelay); + } catch (ReflectiveOperationException e) { + Logging.LOG.log(Level.WARNING, "Cannot use Java 9 Tooltip Installer, fallback to No Duration Tooltip Installer.", e); + return new NoDurationTooltipInstaller(); + } + } + + private final Method setShowDelayMethod; + private final Method setShowDurationMethod; + private final Method setHideDelayMethod; + + private Java9TooltipInstaller(Method setShowDelayMethod, Method setShowDurationMethod, Method setHideDelayMethod) { + this.setShowDelayMethod = setShowDelayMethod; + this.setShowDurationMethod = setShowDurationMethod; + this.setHideDelayMethod = setHideDelayMethod; + } + + @Override + protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { + try { + setShowDelayMethod.invoke(tooltip, new Duration(openDelay)); + setShowDurationMethod.invoke(tooltip, new Duration(visibleDelay)); + setHideDelayMethod.invoke(tooltip, new Duration(closeDelay)); + Tooltip.install(node, tooltip); + return this; + } catch (ReflectiveOperationException e) { + Logging.LOG.log(Level.WARNING, "Cannot use Java 9 Tooltip Installer, fallback to No Duration Tooltip Installer.", e); + return new NoDurationTooltipInstaller().installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip); + } + } + } + + private static final class NoDurationTooltipInstaller extends TooltipInstaller { + @Override + protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { + Tooltip.install(node, tooltip); + return this; + } + } + + private static TooltipInstaller installer = null; + + // FXThread + public static void install(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { + FXUtils.checkFxUserThread(); + + installer = (installer == null ? Java8TooltipInstaller.of() : installer).installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip); + } + + protected abstract TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip); +}