From 6263b791cf1d248c2352749995f21092d833c74b Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 3 Feb 2023 18:07:02 +0000 Subject: [PATCH] Polish various features --- .../comp/storage/store/StoreEntrySection.java | 45 +++-- app/src/main/java/io/xpipe/app/core/App.java | 12 +- .../java/io/xpipe/app/core/AppGreetings.java | 1 + .../app/prefs/ExternalApplicationType.java | 15 +- .../xpipe/app/prefs/ExternalEditorType.java | 11 +- .../resources/lang/translations_en.properties | 8 + .../io/xpipe/app/resources/misc/eula.md | 9 +- .../io/xpipe/app/resources/misc/welcome.md | 11 +- ...nalType.java => ExternalTerminalType.java} | 154 +++++++----------- .../java/io/xpipe/ext/proc/ProcPrefs.java | 18 +- .../resources/lang/translations_en.properties | 5 +- .../fxcomps/util/BindingsHelper.java | 53 ++++++ 12 files changed, 181 insertions(+), 161 deletions(-) rename ext/proc/src/main/java/io/xpipe/ext/proc/{TerminalType.java => ExternalTerminalType.java} (65%) diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java index 898fb55d..1891a36c 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java @@ -25,19 +25,15 @@ public class StoreEntrySection implements StorageFilter.Filterable { } public static ObservableList createTopLevels() { - var topLevel = BindingsHelper.mappedContentBinding( - StoreViewState.get() - .getAllEntries() - .filtered(storeEntryWrapper -> - !storeEntryWrapper.getEntry().getState().isUsable() - || storeEntryWrapper - .getEntry() - .getProvider() - .getParent(storeEntryWrapper - .getEntry() - .getStore()) - == null), - storeEntryWrapper -> create(storeEntryWrapper)); + var filtered = BindingsHelper.filteredContentBinding( + StoreViewState.get().getAllEntries(), + storeEntryWrapper -> !storeEntryWrapper.getEntry().getState().isUsable() + || storeEntryWrapper + .getEntry() + .getProvider() + .getParent(storeEntryWrapper.getEntry().getStore()) + == null); + var topLevel = BindingsHelper.mappedContentBinding(filtered, storeEntryWrapper -> create(storeEntryWrapper)); var ordered = BindingsHelper.orderedContentBinding( topLevel, Comparator.comparing(storeEntrySection -> @@ -51,16 +47,15 @@ public class StoreEntrySection implements StorageFilter.Filterable { return new StoreEntrySection(e, FXCollections.observableArrayList()); } - var children = BindingsHelper.mappedContentBinding( - StoreViewState.get() - .getAllEntries() - .filtered(other -> other.getEntry().getState().isUsable() - && e.getEntry() - .getStore() - .equals(other.getEntry() - .getProvider() - .getParent(other.getEntry().getStore()))), - entry1 -> create(entry1)); + var filtered = BindingsHelper.filteredContentBinding( + StoreViewState.get().getAllEntries(), + other -> other.getEntry().getState().isUsable() + && e.getEntry() + .getStore() + .equals(other.getEntry() + .getProvider() + .getParent(other.getEntry().getStore()))); + var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1)); var ordered = BindingsHelper.orderedContentBinding( children, Comparator.comparing(storeEntrySection -> @@ -94,7 +89,9 @@ public class StoreEntrySection implements StorageFilter.Filterable { storeEntrySection.entry.lastAccessProperty().getValue())); var shown = BindingsHelper.filteredContentBinding( all, - StoreViewState.get().getFilterString().map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s)))); + StoreViewState.get() + .getFilterString() + .map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s)))); var content = new ListBoxViewComp<>(shown, all, (StoreEntrySection e) -> { return e.comp(false).apply(GrowAugment.create(true, false)); }) diff --git a/app/src/main/java/io/xpipe/app/core/App.java b/app/src/main/java/io/xpipe/app/core/App.java index 20ed9dab..8671987d 100644 --- a/app/src/main/java/io/xpipe/app/core/App.java +++ b/app/src/main/java/io/xpipe/app/core/App.java @@ -80,12 +80,12 @@ public class App extends Application { appWindow.show(); // For demo purposes - if (true) { - stage.setX(310); - stage.setY(178); - stage.setWidth(1300); - stage.setHeight(730); - } +// if (true) { +// stage.setX(310); +// stage.setY(178); +// stage.setWidth(1300); +// stage.setHeight(730); +// } } public void focus() { diff --git a/app/src/main/java/io/xpipe/app/core/AppGreetings.java b/app/src/main/java/io/xpipe/app/core/AppGreetings.java index 9fe9e756..7778bea3 100644 --- a/app/src/main/java/io/xpipe/app/core/AppGreetings.java +++ b/app/src/main/java/io/xpipe/app/core/AppGreetings.java @@ -90,6 +90,7 @@ public class AppGreetings { label.setGraphic(cb); AppFont.medium(label); label.setPadding(new Insets(40, 0, 10, 0)); + label.setOnMouseClicked(event -> accepted.set(!accepted.get())); return label; }) .createRegion(); diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java index 9b9d53d8..4973d652 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java @@ -54,28 +54,23 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue { } } - public static class LinuxPathApplication extends ExternalApplicationType { + public static abstract class PathApplication extends ExternalApplicationType { - protected final String command; + protected final String executable; - public LinuxPathApplication(String id, String command) { + public PathApplication(String id, String executable) { super(id); - this.command = command; + this.executable = executable; } public boolean isAvailable() { try (ShellProcessControl pc = ShellStore.local().create().start()) { - return pc.executeBooleanSimpleCommand(pc.getShellType().getWhichCommand(command)); + return pc.executeBooleanSimpleCommand(pc.getShellType().getWhichCommand(executable)); } catch (Exception e) { ErrorEvent.fromThrowable(e).omit().handle(); return false; } } - - @Override - public boolean isSelectable() { - return OsType.getLocal().equals(OsType.LINUX); - } } public abstract static class WindowsFullPathType extends ExternalApplicationType { diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java index 93488551..5e0dad7b 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java @@ -109,7 +109,7 @@ public interface ExternalEditorType extends PrefsChoiceValue { public void launch(Path file) throws Exception; - public static class LinuxPathType extends ExternalApplicationType.LinuxPathApplication implements ExternalEditorType { + public static class LinuxPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType { public LinuxPathType(String id, String command) { super(id, command); @@ -117,9 +117,14 @@ public interface ExternalEditorType extends PrefsChoiceValue { @Override public void launch(Path file) throws IOException { - var list = ShellTypes.getPlatformDefault().executeCommandListWithShell(command + " \"" + file + "\""); + var list = ShellTypes.getPlatformDefault().executeCommandListWithShell(executable + " \"" + file + "\""); new ProcessBuilder(list).start(); } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.LINUX); + } } public abstract static class WindowsFullPathType extends ExternalApplicationType.WindowsFullPathType implements ExternalEditorType { @@ -174,7 +179,7 @@ public interface ExternalEditorType extends PrefsChoiceValue { var env = System.getenv("VISUAL"); if (env != null) { var found = LINUX_EDITORS.stream() - .filter(externalEditorType -> externalEditorType.command.equalsIgnoreCase(env)) + .filter(externalEditorType -> externalEditorType.executable.equalsIgnoreCase(env)) .findFirst() .orElse(null); if (found == null) { diff --git a/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties b/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties index 577e7622..a32724d5 100644 --- a/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties +++ b/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties @@ -69,6 +69,14 @@ recentlyUsed=Recently used programmingLanguages=Programming languages applications=Applications addMore=Add more +vscode=Visual Studio Code +kate=Kate +gedit=GEdit +leafpad=Leafpad +mousepad=Mousepad +pluma=Pluma +textEdit=Text Edit +sublime=Sublime Text newTable=new_table unknown=Unknown editRaw=Edit Raw diff --git a/app/src/main/resources/io/xpipe/app/resources/misc/eula.md b/app/src/main/resources/io/xpipe/app/resources/misc/eula.md index b2541e2b..789fbcb3 100644 --- a/app/src/main/resources/io/xpipe/app/resources/misc/eula.md +++ b/app/src/main/resources/io/xpipe/app/resources/misc/eula.md @@ -46,15 +46,10 @@ specific information we send, please visit https://xpipe.io/privacy_policy. You 1. **Automatic Software Updates.** The Software communicates with its server (and sends information described at the URL above) to determine whether there are any patches, bug fixes, updates, upgrades or other modifications to improve the Software. You agree that the Software may automatically install any such improvements to the Software on your - computer without providing any further notice or receiving any additional consent. This feature may not be disabled. - If you do not want to receive automatic updates, you must uninstall the Software. + computer without providing any further notice or receiving any additional consent. This feature may be disabled. 2. **Error Reports.** In order to help us improve the Software, when the Software encounters certain errors, it will automatically send some information to its server about the error (as described at the URL above). This feature may - not be disabled. If you do not want to send error reports to its server, you must uninstall the Software. -3. **Anonymized Usage Data.** X-Pipe collects anonymized data about your usage of the Software to help us make it more - awesome. Approximately once a day the Software sends such data (as described in more detail at the URL above) to its - server. If you do not want to send anonymized usage data to the server, you may opt out by changing your settings in - the Preferences view. + be disabled. ### Open-Source Notices diff --git a/app/src/main/resources/io/xpipe/app/resources/misc/welcome.md b/app/src/main/resources/io/xpipe/app/resources/misc/welcome.md index 32f2920f..75da7c8c 100644 --- a/app/src/main/resources/io/xpipe/app/resources/misc/welcome.md +++ b/app/src/main/resources/io/xpipe/app/resources/misc/welcome.md @@ -1,14 +1,9 @@ ## Welcome to X-Pipe! -X-Pipe (short for eXtended Pipe) is a tool that enables a fast and an efficient data transfer/exchange between -all types of producers and consumers of data, e.g. different file types, -applications, programming languages, databases, technologies, and more. +Thank you for trying out the X-Pipe Alpha. +You can overview the development status, report issues, and more at the following places: -It is currently in early development and this build is an alpha version. -A lot of features are incomplete or bugged. -You can overview and contribute to the development here: - -#### [Issue Tracker](https://github.com/xpipe-io/xpipe/issues) +#### [GitHub Repository](https://github.com/xpipe-io/xpipe/) #### [Discord Server](https://discord.gg/8y89vS8cRb) diff --git a/ext/proc/src/main/java/io/xpipe/ext/proc/TerminalType.java b/ext/proc/src/main/java/io/xpipe/ext/proc/ExternalTerminalType.java similarity index 65% rename from ext/proc/src/main/java/io/xpipe/ext/proc/TerminalType.java rename to ext/proc/src/main/java/io/xpipe/ext/proc/ExternalTerminalType.java index f86ddfe2..7e78cb04 100644 --- a/ext/proc/src/main/java/io/xpipe/ext/proc/TerminalType.java +++ b/ext/proc/src/main/java/io/xpipe/ext/proc/ExternalTerminalType.java @@ -1,5 +1,6 @@ package io.xpipe.ext.proc; +import io.xpipe.app.prefs.ExternalApplicationType; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.store.ShellStore; @@ -7,18 +8,12 @@ import io.xpipe.extension.event.ErrorEvent; import io.xpipe.extension.prefs.PrefsChoiceValue; import io.xpipe.extension.prefs.PrefsProvider; import io.xpipe.extension.util.ApplicationHelper; -import lombok.AllArgsConstructor; -import lombok.Getter; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.List; -@Getter -@AllArgsConstructor -public abstract class TerminalType implements PrefsChoiceValue { +public interface ExternalTerminalType extends PrefsChoiceValue { - public static final TerminalType CMD = new SimpleType("proc.cmd", "cmd", "cmd.exe") { + public static final ExternalTerminalType CMD = new SimpleType("proc.cmd", "cmd", "cmd.exe") { @Override protected String toCommand(String name, String command) { @@ -31,7 +26,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } }; - public static final TerminalType POWERSHELL = new SimpleType("proc.powershell", "powershell", "PowerShell") { + public static final ExternalTerminalType POWERSHELL = new SimpleType("proc.powershell", "powershell", "PowerShell") { @Override protected String toCommand(String name, String command) { @@ -44,7 +39,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } }; - public static final TerminalType WINDOWS_TERMINAL = + public static final ExternalTerminalType WINDOWS_TERMINAL = new SimpleType("proc.windowsTerminal", "wt.exe", "Windows Terminal") { @Override @@ -58,7 +53,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } }; - public static final TerminalType GNOME_TERMINAL = + public static final ExternalTerminalType GNOME_TERMINAL = new SimpleType("proc.gnomeTerminal", "gnome-terminal", "Gnome Terminal") { @Override @@ -72,7 +67,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } }; - public static final TerminalType KONSOLE = new SimpleType("proc.konsole", "konsole", "Konsole") { + public static final ExternalTerminalType KONSOLE = new SimpleType("proc.konsole", "konsole", "Konsole") { @Override protected String toCommand(String name, String command) { @@ -85,7 +80,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } }; - public static final TerminalType XFCE = new SimpleType("proc.xfce", "xfce4-terminal", "Xfce") { + public static final ExternalTerminalType XFCE = new SimpleType("proc.xfce", "xfce4-terminal", "Xfce") { @Override protected String toCommand(String name, String command) { @@ -98,38 +93,15 @@ public abstract class TerminalType implements PrefsChoiceValue { } }; - public static final TerminalType MACOS_TERMINAL = new MacType("proc.macosTerminal", "Terminal"); + public static final ExternalTerminalType MACOS_TERMINAL = new MacOsType(); - public static final TerminalType ITERM2 = new ITerm2Type(); + public static final ExternalTerminalType ITERM2 = new ITerm2Type(); - public static final TerminalType WARP = new WarpType(); + public static final ExternalTerminalType WARP = new WarpType(); - public static final TerminalType CUSTOM = new TerminalType("app.custom") { + public static final ExternalTerminalType CUSTOM = new CustomType(); - @Override - public void launch(String name, String command) throws Exception { - var custom = - PrefsProvider.get(ProcPrefs.class).customTerminalCommand().getValue(); - if (custom == null || custom.trim().isEmpty()) { - return; - } - - var format = custom.contains("$cmd") ? custom : custom + " $cmd"; - ShellStore.local().create().executeSimpleCommand(format.replace("$cmd", command)); - } - - @Override - public boolean isSelectable() { - return true; - } - - @Override - public boolean isAvailable() { - return false; - } - }; - - public static final List ALL = List.of( + public static final List ALL = List.of( WINDOWS_TERMINAL, POWERSHELL, CMD, @@ -144,42 +116,60 @@ public abstract class TerminalType implements PrefsChoiceValue { .filter(terminalType -> terminalType.isSelectable()) .toList(); - public static TerminalType getDefault() { + public static ExternalTerminalType getDefault() { return ALL.stream() .filter(terminalType -> terminalType.isAvailable()) .findFirst() .orElse(null); } - private String id; - public abstract void launch(String name, String command) throws Exception; - public abstract boolean isSelectable(); + static class MacOsType extends ExternalApplicationType.MacApplication implements ExternalTerminalType { - public abstract boolean isAvailable(); + public MacOsType() { + super("proc.macosTerminal", "Terminal"); + } - static class MacType extends TerminalType { + @Override + public void launch(String name, String command) throws Exception { + var custom = + PrefsProvider.get(ProcPrefs.class).customTerminalCommand().getValue(); + if (custom == null || custom.trim().isEmpty()) { + return; + } - private final String terminalName; + var format = custom.contains("$cmd") ? custom : custom + " $cmd"; + try (var pc = ShellStore.local().create().start()) { + var toExecute = format.replace("$cmd", command); + if (pc.getOsType().equals(OsType.WINDOWS)) { + toExecute = "start \"" + name + "\" " + toExecute; + } else { + toExecute = "nohup " + toExecute + " /dev/null & disown"; + } + pc.executeSimpleCommand(toExecute); + } + } + } - public MacType(String id, String terminalName) { - super(id); - this.terminalName = terminalName; + static class CustomType extends ExternalApplicationType implements ExternalTerminalType { + + public CustomType() { + super("proc.custom"); } @Override public void launch(String name, String command) throws Exception { try (ShellProcessControl pc = ShellStore.local().create().start()) { var suffix = command.equals(pc.getShellType().getNormalOpenCommand()) ? "\"\"" : "\"" + command + "\""; - var cmd = "osascript -e 'tell app \"" + terminalName + "\" to do script " + suffix + "'"; + var cmd = "osascript -e 'tell app \"" + "Terminal" + "\" to do script " + suffix + "'"; pc.executeSimpleCommand(cmd); } } @Override public boolean isSelectable() { - return OsType.getLocal().equals(OsType.MAC); + return true; } @Override @@ -188,10 +178,10 @@ public abstract class TerminalType implements PrefsChoiceValue { } } - static class ITerm2Type extends TerminalType { + static class ITerm2Type extends ExternalApplicationType.MacApplication implements ExternalTerminalType { public ITerm2Type() { - super("proc.iterm2"); + super("proc.iterm2", "iTerm2"); } @Override @@ -200,7 +190,6 @@ public abstract class TerminalType implements PrefsChoiceValue { var cmd = String.format( """ osascript - "$@" < terminalType = new SimpleObjectProperty<>(); - private final SimpleListProperty terminalTypeList = new SimpleListProperty<>( - FXCollections.observableArrayList(PrefsChoiceValue.getSupported(TerminalType.class))); - private final SingleSelectionField terminalTypeControl = Field.ofSingleSelectionType( + private final ObjectProperty terminalType = new SimpleObjectProperty<>(); + private final SimpleListProperty terminalTypeList = new SimpleListProperty<>( + FXCollections.observableArrayList(PrefsChoiceValue.getSupported(ExternalTerminalType.class))); + private final SingleSelectionField terminalTypeControl = Field.ofSingleSelectionType( terminalTypeList, terminalType) .render(() -> new TranslatableComboBoxControl<>()); @@ -37,9 +37,9 @@ public class ProcPrefs extends PrefsProvider { private final StringProperty customTerminalCommand = new SimpleStringProperty(""); private final StringField customTerminalCommandControl = editable( StringField.ofStringType(customTerminalCommand).render(() -> new SimpleTextControl()), - terminalType.isEqualTo(TerminalType.CUSTOM)); + terminalType.isEqualTo(ExternalTerminalType.CUSTOM)); - public ObservableValue terminalType() { + public ObservableValue terminalType() { return terminalType; } @@ -53,19 +53,19 @@ public class ProcPrefs extends PrefsProvider { List.of("integrations"), "proc.terminal", Setting.of("app.defaultProgram", terminalTypeControl, terminalType), - TerminalType.class); + ExternalTerminalType.class); handler.addSetting( List.of("integrations"), "proc.terminal", Setting.of("proc.customTerminalCommand", customTerminalCommandControl, customTerminalCommand) - .applyVisibility(VisibilityProperty.of(terminalType.isEqualTo(TerminalType.CUSTOM))), + .applyVisibility(VisibilityProperty.of(terminalType.isEqualTo(ExternalTerminalType.CUSTOM))), String.class); } @Override public void init() { if (terminalType.get() == null) { - terminalType.set(TerminalType.getDefault()); + terminalType.set(ExternalTerminalType.getDefault()); } } } diff --git a/ext/proc/src/main/resources/io/xpipe/ext/proc/resources/lang/translations_en.properties b/ext/proc/src/main/resources/io/xpipe/ext/proc/resources/lang/translations_en.properties index b7d82acd..4e590b59 100644 --- a/ext/proc/src/main/resources/io/xpipe/ext/proc/resources/lang/translations_en.properties +++ b/ext/proc/src/main/resources/io/xpipe/ext/proc/resources/lang/translations_en.properties @@ -88,4 +88,7 @@ keyPassword=Key Password key=Key installConnector=Install Connector konsole=Konsole -xfce=Xfce \ No newline at end of file +xfce=Xfce +macosTerminal=Terminal +iterm2=iTerm2 +warp=Warp \ No newline at end of file diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/util/BindingsHelper.java b/extension/src/main/java/io/xpipe/extension/fxcomps/util/BindingsHelper.java index 465746c2..0a26128a 100644 --- a/extension/src/main/java/io/xpipe/extension/fxcomps/util/BindingsHelper.java +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/util/BindingsHelper.java @@ -1,10 +1,14 @@ package io.xpipe.extension.fxcomps.util; +import io.xpipe.extension.util.ThreadHelper; import javafx.beans.binding.Binding; +import javafx.beans.binding.ListBinding; +import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import lombok.Value; import java.lang.ref.WeakReference; import java.util.*; @@ -14,6 +18,37 @@ import java.util.function.Predicate; public class BindingsHelper { + private static final Set REFERENCES = Collections.newSetFromMap(new ConcurrentHashMap()); + + @Value + private static class ReferenceEntry { + + WeakReference source; + Object target; + + public boolean canGc() { + return source.get() == null; + } + } + + static { + ThreadHelper.create("referenceGC", true, () -> { + while (true) { + for (ReferenceEntry reference : REFERENCES) { + if (reference.canGc()) { + REFERENCES.remove(reference); + } + } + ThreadHelper.sleep(1000); + } + }) + .start(); + } + + public static void linkPersistently(Object source, Object target) { + REFERENCES.add(new ReferenceEntry(new WeakReference<>(source), target)); + } + /* TODO: Proper cleanup. Maybe with a separate thread? */ @@ -30,6 +65,17 @@ public class BindingsHelper { return binding; } + public static > T persist(T binding) { + var dependencies = new HashSet(); + while (dependencies.addAll(binding.getDependencies().stream() + .map(o -> (javafx.beans.Observable) o) + .toList())) { + } + dependencies.add(binding); + BINDINGS.put(new WeakReference<>(binding), dependencies); + return binding; + } + public static void bindContent(ObservableList l1, ObservableList l2) { setContent(l1, l2); l2.addListener((ListChangeListener) c -> { @@ -56,6 +102,7 @@ public class BindingsHelper { l2.addListener((ListChangeListener) c -> { runnable.run(); }); + linkPersistently(l2, l1); return l1; } @@ -68,9 +115,14 @@ public class BindingsHelper { l2.addListener((ListChangeListener) c -> { runnable.run(); }); + linkPersistently(l2, l1); return l1; } + public static ObservableList filteredContentBinding(ObservableList l2,Predicate predicate) { + return filteredContentBinding(l2, new SimpleObjectProperty<>(predicate)); + } + public static ObservableList filteredContentBinding(ObservableList l2, ObservableValue> predicate) { ObservableList l1 = FXCollections.observableList(new ArrayList<>()); Runnable runnable = () -> { @@ -83,6 +135,7 @@ public class BindingsHelper { predicate.addListener((c,o,n) -> { runnable.run(); }); + linkPersistently(l2, l1); return l1; }