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 0cb5c433f0..9708ad03ea 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -23,11 +23,9 @@ import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.WeakInvalidationListener; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.Property; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.beans.value.WeakChangeListener; -import javafx.beans.value.WritableValue; +import javafx.beans.value.*; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; @@ -54,6 +52,7 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.construct.JFXHyperlink; +import org.jackhuang.hmcl.util.CrashReporter; import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.ResourceNotFoundError; import org.jackhuang.hmcl.util.io.FileUtils; @@ -401,7 +400,7 @@ else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && new File("/usr/bin/xdg-ope // Fallback to java.awt.Desktop::open try { - java.awt.Desktop.getDesktop().open(file); + Desktop.getDesktop().open(file); } catch (Throwable e) { LOG.error("Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e); } @@ -483,7 +482,7 @@ public static void openLink(String link) { } } try { - java.awt.Desktop.getDesktop().browse(new URI(link)); + Desktop.getDesktop().browse(new URI(link)); } catch (Throwable e) { if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) try { @@ -676,6 +675,61 @@ public static void unbindEnum(JFXComboBox> comboBox) { comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener); } + public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) { + int itemCount = children.length; + int childSelectedCount = 0; + for (BooleanProperty child : children) { + if (child.get()) + childSelectedCount++; + } + + allEnabled.set(childSelectedCount == itemCount); + + class Listener implements InvalidationListener { + private int childSelectedCount; + private boolean updating = false; + + public Listener(int childSelectedCount) { + this.childSelectedCount = childSelectedCount; + } + + @Override + public void invalidated(Observable observable) { + if (updating) + return; + + updating = true; + try { + boolean value = ((BooleanProperty) observable).get(); + + if (observable == allEnabled) { + for (BooleanProperty child : children) { + child.setValue(value); + } + childSelectedCount = value ? itemCount : 0; + } else { + if (value) + childSelectedCount++; + else + childSelectedCount--; + + allEnabled.set(childSelectedCount == itemCount); + } + } finally { + updating = false; + } + } + } + + InvalidationListener listener = new Listener(childSelectedCount); + + WeakInvalidationListener weakListener = new WeakInvalidationListener(listener); + allEnabled.addListener(listener); + for (BooleanProperty child : children) { + child.addListener(weakListener); + } + } + public static void setIcon(Stage stage) { String icon; if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { @@ -691,7 +745,7 @@ public static void setIcon(Stage stage) { * * @param url the url of image. The image resource should be a file within the jar. * @return the image resource within the jar. - * @see org.jackhuang.hmcl.util.CrashReporter + * @see CrashReporter * @see ResourceNotFoundError */ public static Image newBuiltinImage(String url) { @@ -715,7 +769,7 @@ public static Image newBuiltinImage(String url) { * algorithm or a faster one when scaling this image to fit within * the specified bounding box * @return the image resource within the jar. - * @see org.jackhuang.hmcl.util.CrashReporter + * @see CrashReporter * @see ResourceNotFoundError */ public static Image newBuiltinImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) { @@ -933,7 +987,7 @@ public static List parseSegment(String segment, Consumer hyperlink Element r = doc.getDocumentElement(); NodeList children = r.getChildNodes(); - List texts = new ArrayList<>(); + List texts = new ArrayList<>(); for (int i = 0; i < children.getLength(); i++) { org.w3c.dom.Node node = children.item(i); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java index ac85724790..791ba21c4d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java @@ -41,7 +41,6 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.PageCloseEvent; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; -import org.jackhuang.hmcl.util.javafx.SelectionBinding; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.io.CSVTable; @@ -67,7 +66,6 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage { private final ModManager modManager; private final ObservableList objects; - private final SelectionBinding allEnabled; // Keep a reference. @SuppressWarnings("unchecked") public ModUpdatesPage(ModManager modManager, List updates) { @@ -100,8 +98,7 @@ public ModUpdatesPage(ModManager modManager, List update setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty); objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList())); - - allEnabled = new SelectionBinding(allEnabledBox.selectedProperty(), objects.stream().map(o -> o.enabled).collect(Collectors.toList())); + FXUtils.bindAllEnabled(allEnabledBox.selectedProperty(), objects.stream().map(o -> o.enabled).toArray(BooleanProperty[]::new)); TableView table = new TableView<>(objects); table.setEditable(true); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionBinding.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionBinding.java deleted file mode 100644 index 7f0144b724..0000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionBinding.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.jackhuang.hmcl.util.javafx; - -import javafx.beans.property.BooleanProperty; -import javafx.beans.value.ObservableValue; - -import java.util.List; -import java.util.function.Consumer; - -public final class SelectionBinding { - private final BooleanProperty allSelected; - - private final List children; - - private int childSelectedCount = 0; - - private boolean updating = false; - - public SelectionBinding(BooleanProperty allSelected, List children) { - this.allSelected = allSelected; - this.children = children; - int itemCount = children.size(); - - for (BooleanProperty child : children) { - if (child.get()) { - childSelectedCount++; - } - - onChange(child, wrap(value -> { - if (value) { - childSelectedCount++; - } else { - childSelectedCount--; - } - - allSelected.set(childSelectedCount == itemCount); - })); - } - - allSelected.set(childSelectedCount == itemCount); - - onChange(allSelected, wrap(value -> { - for (BooleanProperty child : children) { - child.setValue(value); - } - if (value) { - childSelectedCount = itemCount; - } else { - childSelectedCount = 0; - } - })); - } - - private static void onChange(ObservableValue value, Consumer consumer) { - value.addListener((a, b, c) -> consumer.accept(c)); - } - - private Consumer wrap(Consumer c) { - return value -> { - if (!updating) { - try { - updating = true; - - c.accept(value); - } finally { - updating = false; - } - } - }; - } -}