diff --git a/CHANGELOG.md b/CHANGELOG.md index c39f3be5..ee09661a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ### 0.5.1-SNAPSHOT (TBD) +#### Bugfixes +* Fixed weak listeners setup in `jpro-file` module. + ---------------------- ### 0.5.0 (November 25, 2024) diff --git a/jpro-file/src/main/java/one/jpro/platform/file/dropper/WebFileDropper.java b/jpro-file/src/main/java/one/jpro/platform/file/dropper/WebFileDropper.java index 8eeb5c2a..80d284f1 100644 --- a/jpro-file/src/main/java/one/jpro/platform/file/dropper/WebFileDropper.java +++ b/jpro-file/src/main/java/one/jpro/platform/file/dropper/WebFileDropper.java @@ -58,8 +58,7 @@ public WebFileDropper(Node node) { // Wrap the listener into a WeakInvalidationListener to avoid memory leaks, // that can occur if observers are not unregistered from observed objects after use. - final WeakInvalidationListener weakFileDragOverListener = new WeakInvalidationListener(fileDragOverListener); - multiFileUploader.fileDragOverProperty().addListener(weakFileDragOverListener); + multiFileUploader.fileDragOverProperty().addListener(new WeakInvalidationListener(fileDragOverListener)); } @Override diff --git a/jpro-file/src/main/java/one/jpro/platform/file/picker/BaseFilePicker.java b/jpro-file/src/main/java/one/jpro/platform/file/picker/BaseFilePicker.java index 0038b9f5..063eb5f7 100644 --- a/jpro-file/src/main/java/one/jpro/platform/file/picker/BaseFilePicker.java +++ b/jpro-file/src/main/java/one/jpro/platform/file/picker/BaseFilePicker.java @@ -1,11 +1,9 @@ package one.jpro.platform.file.picker; -import com.jpro.webapi.WebAPI; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; -import javafx.beans.value.WeakChangeListener; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -102,19 +100,6 @@ final ExtensionFilter findSelectedFilter() { } } - /** - * Synchronizes the selected {@link ExtensionFilter} between this file picker and the native {@link FileChooser}. - * This ensures that changes in one are reflected in the other without causing infinite update loops. - * - * @param fileChooser the native file chooser to synchronize with; must not be {@code null} - */ - final void synchronizeSelectedExtensionFilter(FileChooser fileChooser) { - fileChooser.selectedExtensionFilterProperty() - .addListener(new WeakChangeListener<>(getNativeSelectedExtensionFilterChangeListener())); - selectedExtensionFilterProperty() - .addListener(new WeakChangeListener<>(getSelectedExtensionFilterChangeListener(fileChooser))); - } - /** * Creates a {@link ChangeListener} that listens for changes in the native {@link FileChooser}'s * selected extension filter and updates the corresponding property in this file picker. @@ -122,7 +107,7 @@ final void synchronizeSelectedExtensionFilter(FileChooser fileChooser) { * @return a change listener for the native file chooser's selected extension filter */ @NotNull - private ChangeListener getNativeSelectedExtensionFilterChangeListener() { + final ChangeListener getNativeSelectedExtensionFilterChangeListener() { return (observable, oldFilter, newFilter) -> { if (updatingFromProperty) { return; @@ -152,7 +137,7 @@ private ChangeListener getNativeSelectedExtensionFi * @return a change listener for the native file chooser's selected extension filter */ @NotNull - private ChangeListener getSelectedExtensionFilterChangeListener(FileChooser fileChooser) { + final ChangeListener getSelectedExtensionFilterChangeListener(FileChooser fileChooser) { return (observable, oldFilter, newFilter) -> { if (updatingFromFileChooser) { return; @@ -203,33 +188,4 @@ final ListChangeListener getNativeExtensionFilterListChangeList } }; } - - /** - * Creates a {@link ListChangeListener} that listens for changes in the list of {@link ExtensionFilter} - * instances and updates the web-based file uploader's supported extensions accordingly. - *

- * This listener handles both additions and removals of extension filters. - *

- * - * @param multiFileUploader the web file uploader whose supported extensions will be updated; must not be {@code null} - * @return a list change listener for updating the web file uploader's supported extensions - */ - @NotNull - final ListChangeListener getWebExtensionFilterListChangeListener(WebAPI.MultiFileUploader multiFileUploader) { - return change -> { - while (change.next()) { - if (change.wasAdded()) { - for (ExtensionFilter extensionFilter : change.getAddedSubList()) { - extensionFilter.extensions() - .forEach(multiFileUploader.supportedExtensions()::add); - } - } else if (change.wasRemoved()) { - for (ExtensionFilter extensionFilter : change.getRemoved()) { - extensionFilter.extensions() - .forEach(multiFileUploader.supportedExtensions()::remove); - } - } - } - }; - } } diff --git a/jpro-file/src/main/java/one/jpro/platform/file/picker/NativeFileOpenPicker.java b/jpro-file/src/main/java/one/jpro/platform/file/picker/NativeFileOpenPicker.java index ba8e397f..037a8c35 100644 --- a/jpro-file/src/main/java/one/jpro/platform/file/picker/NativeFileOpenPicker.java +++ b/jpro-file/src/main/java/one/jpro/platform/file/picker/NativeFileOpenPicker.java @@ -4,12 +4,16 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.WeakChangeListener; +import javafx.collections.ListChangeListener; import javafx.collections.WeakListChangeListener; import javafx.scene.Node; import javafx.scene.control.SelectionMode; import javafx.scene.input.MouseEvent; import javafx.stage.FileChooser; import javafx.stage.Window; +import one.jpro.platform.file.ExtensionFilter; import one.jpro.platform.file.FileSource; import one.jpro.platform.file.NativeFileSource; import one.jpro.platform.file.util.NodeUtils; @@ -28,7 +32,13 @@ */ public class NativeFileOpenPicker extends BaseFileOpenPicker { - private final FileChooser fileChooser; + private final FileChooser fileChooser = new FileChooser(); + private final ChangeListener nativeSelectedExtensionFilterChangeListener = + getNativeSelectedExtensionFilterChangeListener(); + private final ChangeListener selectedExtensionFilterChangeListener = + getSelectedExtensionFilterChangeListener(fileChooser); + private final ListChangeListener nativeExtensionFilterListChangeListener = + getNativeExtensionFilterListChangeListener(fileChooser); private List nativeFileSources = List.of(); /** @@ -39,17 +49,13 @@ public class NativeFileOpenPicker extends BaseFileOpenPicker { public NativeFileOpenPicker(Node node) { super(node); - // Initialize the FileChooser - fileChooser = new FileChooser(); - // Initializes synchronization between the FileChooser's selectedExtensionFilterProperty // and the FilePicker's selectedExtensionFilter property. synchronizeSelectedExtensionFilter(fileChooser); // Wrap the listener into a WeakListChangeListener to avoid memory leaks, // that can occur if observers are not unregistered from observed objects after use. - getExtensionFilters().addListener( - new WeakListChangeListener<>(getNativeExtensionFilterListChangeListener(fileChooser))); + getExtensionFilters().addListener(new WeakListChangeListener<>(nativeExtensionFilterListChangeListener)); // Define the action that should be performed when the user clicks on the node. NodeUtils.addEventHandler(node, MouseEvent.MOUSE_CLICKED, actionEvent -> { @@ -82,6 +88,19 @@ public NativeFileOpenPicker(Node node) { }); } + /** + * Synchronizes the selected {@link ExtensionFilter} between this file picker and the native {@link FileChooser}. + * This ensures that changes in one are reflected in the other without causing infinite update loops. + * + * @param fileChooser the native file chooser to synchronize with; must not be {@code null} + */ + final void synchronizeSelectedExtensionFilter(FileChooser fileChooser) { + fileChooser.selectedExtensionFilterProperty() + .addListener(new WeakChangeListener<>(nativeSelectedExtensionFilterChangeListener)); + selectedExtensionFilterProperty() + .addListener(new WeakChangeListener<>(selectedExtensionFilterChangeListener)); + } + @Override public final String getTitle() { return fileChooser.getTitle(); diff --git a/jpro-file/src/main/java/one/jpro/platform/file/picker/NativeFileSavePicker.java b/jpro-file/src/main/java/one/jpro/platform/file/picker/NativeFileSavePicker.java index 5ab052fa..2c2dfadf 100644 --- a/jpro-file/src/main/java/one/jpro/platform/file/picker/NativeFileSavePicker.java +++ b/jpro-file/src/main/java/one/jpro/platform/file/picker/NativeFileSavePicker.java @@ -3,11 +3,15 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.WeakChangeListener; +import javafx.collections.ListChangeListener; import javafx.collections.WeakListChangeListener; import javafx.scene.Node; import javafx.scene.Scene; import javafx.stage.FileChooser; import javafx.stage.Window; +import one.jpro.platform.file.ExtensionFilter; import java.io.File; import java.util.concurrent.CompletableFuture; @@ -22,22 +26,37 @@ */ public class NativeFileSavePicker extends BaseFileSavePicker { - private final FileChooser fileChooser; + private final FileChooser fileChooser = new FileChooser(); + private final ChangeListener nativeSelectedExtensionFilterChangeListener = + getNativeSelectedExtensionFilterChangeListener(); + private final ChangeListener selectedExtensionFilterChangeListener = + getSelectedExtensionFilterChangeListener(fileChooser); + private final ListChangeListener nativeExtensionFilterListChangeListener = + getNativeExtensionFilterListChangeListener(fileChooser); public NativeFileSavePicker(Node node) { super(node); - // Initialize the FileChooser - fileChooser = new FileChooser(); - // Initializes synchronization between the FileChooser's selectedExtensionFilterProperty // and the FilePicker's selectedExtensionFilter property. synchronizeSelectedExtensionFilter(fileChooser); // Wrap the listener into a WeakListChangeListener to avoid memory leaks, // that can occur if observers are not unregistered from observed objects after use. - getExtensionFilters().addListener( - new WeakListChangeListener<>(getNativeExtensionFilterListChangeListener(fileChooser))); + getExtensionFilters().addListener(new WeakListChangeListener<>(nativeExtensionFilterListChangeListener)); + } + + /** + * Synchronizes the selected {@link ExtensionFilter} between this file picker and the native {@link FileChooser}. + * This ensures that changes in one are reflected in the other without causing infinite update loops. + * + * @param fileChooser the native file chooser to synchronize with; must not be {@code null} + */ + final void synchronizeSelectedExtensionFilter(FileChooser fileChooser) { + fileChooser.selectedExtensionFilterProperty() + .addListener(new WeakChangeListener<>(nativeSelectedExtensionFilterChangeListener)); + selectedExtensionFilterProperty() + .addListener(new WeakChangeListener<>(selectedExtensionFilterChangeListener)); } @Override diff --git a/jpro-file/src/main/java/one/jpro/platform/file/picker/WebFileOpenPicker.java b/jpro-file/src/main/java/one/jpro/platform/file/picker/WebFileOpenPicker.java index a79596b1..cef0734b 100644 --- a/jpro-file/src/main/java/one/jpro/platform/file/picker/WebFileOpenPicker.java +++ b/jpro-file/src/main/java/one/jpro/platform/file/picker/WebFileOpenPicker.java @@ -5,16 +5,20 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.collections.ListChangeListener; import javafx.collections.WeakListChangeListener; import javafx.scene.Node; import javafx.scene.control.SelectionMode; +import one.jpro.platform.file.ExtensionFilter; import one.jpro.platform.file.FileSource; import one.jpro.platform.file.WebFileSource; import one.jpro.platform.file.util.NodeUtils; import java.io.File; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.Consumer; /** @@ -27,6 +31,7 @@ public class WebFileOpenPicker extends BaseFileOpenPicker { private final WebAPI.MultiFileUploader multiFileUploader; + private final ListChangeListener webExtensionFilterListChangeListener; private List webFileSources = List.of(); /** @@ -41,10 +46,17 @@ public WebFileOpenPicker(Node node) { WebAPI.makeMultiFileUploadNodeStatic(node)); multiFileUploader.setSelectFileOnClick(true); + webExtensionFilterListChangeListener = change -> { + final List supportedExtensionsList = change.getList().stream() + .flatMap(ext -> ext.extensions().stream()) + .toList(); + final Set supportedExtensionsSet = new HashSet<>(supportedExtensionsList); // Remove duplicates + multiFileUploader.supportedExtensions().setAll(supportedExtensionsSet.stream().toList()); + }; + // Wrap the listener into a WeakListChangeListener to avoid memory leaks, // that can occur if observers are not unregistered from observed objects after use. - getExtensionFilters().addListener( - new WeakListChangeListener<>(getWebExtensionFilterListChangeListener(multiFileUploader))); + getExtensionFilters().addListener(new WeakListChangeListener<>(webExtensionFilterListChangeListener)); } // title property