From 83f7e61d3772cacfa31fefee988f2d9c53773a8a Mon Sep 17 00:00:00 2001 From: yushijinhun Date: Mon, 1 Oct 2018 17:03:34 +0800 Subject: [PATCH] Add selectedItemPropertyFor(ToggleGroup) --- .../org/jackhuang/hmcl/setting/Accounts.java | 4 +- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 4 +- .../java/org/jackhuang/hmcl/ui/ListPage.java | 4 +- .../org/jackhuang/hmcl/ui/SettingsPage.java | 2 +- .../util/javafx/MappedObservableList.java | 16 --- .../javafx/ReadWriteComposedProperty.java | 5 + .../hmcl/util/javafx/ReferenceHolder.java | 35 ++++++ .../util/javafx/SelectedItemProperties.java | 119 ++++++++++++++++++ .../SelectionModelSelectedItemProperty.java | 48 ------- 9 files changed, 166 insertions(+), 71 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReferenceHolder.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectedItemProperties.java delete mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionModelSelectedItemProperty.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 550be7665..23cdd0ef3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -91,9 +91,9 @@ public final class Accounts { } private static ObservableList accounts = observableArrayList(account -> new Observable[] { account }); - private static ReadOnlyListWrapper accountsWrapper = new ReadOnlyListWrapper<>(accounts); + private static ReadOnlyListWrapper accountsWrapper = new ReadOnlyListWrapper<>(Accounts.class, "accounts", accounts); - private static ObjectProperty selectedAccount = new SimpleObjectProperty() { + private static ObjectProperty selectedAccount = new SimpleObjectProperty(Accounts.class, "selectedAccount") { { accounts.addListener(onInvalidating(this::invalidated)); } 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 bad63bbf7..a60bd048d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -47,7 +47,7 @@ import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.function.ExceptionalSupplier; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.javafx.SelectionModelSelectedItemProperty; +import org.jackhuang.hmcl.util.javafx.SelectedItemProperties; import org.jackhuang.hmcl.util.platform.OperatingSystem; import java.io.File; @@ -348,7 +348,7 @@ public final class FXUtils { * @param comboBox the combo box being bound with {@code property}. * @param property the property being bound with {@code combo box}. * @see #unbindEnum(JFXComboBox) - * @deprecated Use {@link SelectionModelSelectedItemProperty#selectedItemPropertyFor(ComboBox)} + * @deprecated Use {@link SelectedItemProperties#selectedItemPropertyFor(ComboBox)} */ @SuppressWarnings("unchecked") @Deprecated diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java index 556b2023d..3c2fcb11e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java @@ -27,8 +27,8 @@ import javafx.scene.control.Control; import javafx.scene.control.Skin; public abstract class ListPage extends Control { - private final ListProperty items = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final BooleanProperty loading = new SimpleBooleanProperty(false); + private final ListProperty items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); + private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false); public abstract void add(); 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 c6cb464af..04467924e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java @@ -47,7 +47,7 @@ import java.util.Optional; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import static org.jackhuang.hmcl.util.javafx.SelectionModelSelectedItemProperty.selectedItemPropertyFor; +import static org.jackhuang.hmcl.util.javafx.SelectedItemProperties.selectedItemPropertyFor; public final class SettingsPage extends SettingsView implements DecoratorPage { private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("settings.launcher")); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MappedObservableList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MappedObservableList.java index 4aad5a869..debb8a882 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MappedObservableList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MappedObservableList.java @@ -17,8 +17,6 @@ */ package org.jackhuang.hmcl.util.javafx; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -37,20 +35,6 @@ public final class MappedObservableList { private MappedObservableList() { } - private static class ReferenceHolder implements InvalidationListener { - @SuppressWarnings("unused") - private Object ref; - - ReferenceHolder(Object ref) { - this.ref = ref; - } - - @Override - public void invalidated(Observable observable) { - // no-op - } - } - private static class MappedObservableListUpdater implements ListChangeListener { private ObservableList origin; private ObservableList target; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReadWriteComposedProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReadWriteComposedProperty.java index b64e198ad..bc69d1a9b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReadWriteComposedProperty.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReadWriteComposedProperty.java @@ -36,6 +36,11 @@ public class ReadWriteComposedProperty extends SimpleObjectProperty { private ChangeListener listener; public ReadWriteComposedProperty(ObservableValue readSource, Consumer writeTarget) { + this(null, "", readSource, writeTarget); + } + + public ReadWriteComposedProperty(Object bean, String name, ObservableValue readSource, Consumer writeTarget) { + super(bean, name); this.readSource = readSource; this.writeTarget = writeTarget; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReferenceHolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReferenceHolder.java new file mode 100644 index 000000000..7e3263866 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReferenceHolder.java @@ -0,0 +1,35 @@ +/* + * 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 javafx.beans.InvalidationListener; +import javafx.beans.Observable; + +class ReferenceHolder implements InvalidationListener { + @SuppressWarnings("unused") + private Object ref; + + public ReferenceHolder(Object ref) { + this.ref = ref; + } + + @Override + public void invalidated(Observable observable) { + // no-op + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectedItemProperties.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectedItemProperties.java new file mode 100644 index 000000000..a84cad264 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectedItemProperties.java @@ -0,0 +1,119 @@ +/* + * 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 static org.jackhuang.hmcl.util.Pair.pair; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; + +import javafx.beans.InvalidationListener; +import javafx.beans.WeakInvalidationListener; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.Property; +import javafx.scene.control.ComboBox; +import javafx.scene.control.SelectionModel; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; + +/** + * @author yushijinhun + */ +public final class SelectedItemProperties { + + private static final String PROP_PREFIX = SelectedItemProperties.class.getName(); + + @SuppressWarnings("unchecked") + public static ObjectProperty selectedItemPropertyFor(ComboBox comboBox) { + return (ObjectProperty) comboBox.getProperties().computeIfAbsent( + PROP_PREFIX + ".comboxBox.selectedItem", + any -> createPropertyForSelectionModel(comboBox, comboBox.selectionModelProperty())); + } + + private static ObjectProperty createPropertyForSelectionModel(Object bean, Property> modelProperty) { + return new ReadWriteComposedProperty<>(bean, "extra.selectedItem", + MultiStepBinding.of(modelProperty) + .flatMap(SelectionModel::selectedItemProperty), + modelProperty.getValue()::select); + } + + @SuppressWarnings("unchecked") + public static ObjectProperty selectedTogglePropertyFor(ToggleGroup toggleGroup) { + return (ObjectProperty) toggleGroup.getProperties().computeIfAbsent( + PROP_PREFIX + ".toggleGroup.selectedToggle", + any -> createPropertyForToggleGroup(toggleGroup)); + } + + private static ObjectProperty createPropertyForToggleGroup(ToggleGroup toggleGroup) { + return new ReadWriteComposedProperty<>(toggleGroup, "extra.selectedToggle", + toggleGroup.selectedToggleProperty(), + toggleGroup::selectToggle); + } + + @SuppressWarnings("unchecked") + public static ObjectProperty selectedItemPropertyFor(ToggleGroup toggleGroup, Class userdataType) { + return (ObjectProperty) toggleGroup.getProperties().computeIfAbsent( + pair(PROP_PREFIX + ".toggleGroup.selectedItem", userdataType), + any -> createMappedPropertyForToggleGroup( + toggleGroup, + toggle -> toggle == null ? null : userdataType.cast(toggle.getUserData()))); + } + + private static ObjectProperty createMappedPropertyForToggleGroup(ToggleGroup toggleGroup, Function mapper) { + ObjectProperty selectedToggle = selectedTogglePropertyFor(toggleGroup); + AtomicReference> pendingItemHolder = new AtomicReference<>(); + + Consumer itemSelector = newItem -> { + Optional toggleToSelect = toggleGroup.getToggles().stream() + .filter(toggle -> Objects.equals(newItem, mapper.apply(toggle))) + .findFirst(); + if (toggleToSelect.isPresent()) { + pendingItemHolder.set(null); + selectedToggle.set(toggleToSelect.get()); + } else { + // We are asked to select an nonexistent item. + // However, this item may become available in the future. + // So here we store it, and once the associated toggle becomes available, we will update the selection. + pendingItemHolder.set(Optional.ofNullable(newItem)); + selectedToggle.set(null); + } + }; + + ReadWriteComposedProperty property = new ReadWriteComposedProperty<>(toggleGroup, "extra.selectedItem", + MultiStepBinding.of(selectedTogglePropertyFor(toggleGroup)) + .map(mapper), + itemSelector); + + InvalidationListener onTogglesChanged = any -> { + Optional pendingItem = pendingItemHolder.get(); + if (pendingItem != null) { + itemSelector.accept(pendingItem.orElse(null)); + } + }; + toggleGroup.getToggles().addListener(new WeakInvalidationListener(onTogglesChanged)); + property.addListener(new ReferenceHolder(onTogglesChanged)); + + return property; + } + + private SelectedItemProperties() { + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionModelSelectedItemProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionModelSelectedItemProperty.java deleted file mode 100644 index dad78b254..000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionModelSelectedItemProperty.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 javafx.beans.property.ObjectProperty; -import javafx.beans.property.Property; -import javafx.scene.control.ComboBox; -import javafx.scene.control.SelectionModel; - -/** - * @author yushijinhun - */ -public final class SelectionModelSelectedItemProperty { - - private static final String NODE_PROPERTY = SelectionModelSelectedItemProperty.class.getName() + ".instance"; - - @SuppressWarnings("unchecked") - public static ObjectProperty selectedItemPropertyFor(ComboBox comboBox) { - return (ObjectProperty) comboBox.getProperties().computeIfAbsent( - NODE_PROPERTY, - any -> createSelectedItemProperty(comboBox.selectionModelProperty())); - } - - private static ObjectProperty createSelectedItemProperty(Property> modelProperty) { - return new ReadWriteComposedProperty<>( - MultiStepBinding.of(modelProperty) - .flatMap(SelectionModel::selectedItemProperty), - newValue -> modelProperty.getValue().select(newValue)); - } - - private SelectionModelSelectedItemProperty() { - } -}