diff --git a/app/src/main/java/io/xpipe/app/comp/store/DsStoreProviderChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/store/DataStoreProviderChoiceComp.java similarity index 52% rename from app/src/main/java/io/xpipe/app/comp/store/DsStoreProviderChoiceComp.java rename to app/src/main/java/io/xpipe/app/comp/store/DataStoreProviderChoiceComp.java index 8993234be..c8d6b92ec 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/DsStoreProviderChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/DataStoreProviderChoiceComp.java @@ -1,17 +1,16 @@ package io.xpipe.app.comp.store; -import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.ext.DataStoreProviders; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.CompStructure; import io.xpipe.app.fxcomps.SimpleCompStructure; import io.xpipe.app.prefs.AppPrefs; -import io.xpipe.app.util.CustomComboBoxBuilder; import io.xpipe.app.util.JfxHelper; import javafx.beans.property.Property; -import javafx.scene.Node; import javafx.scene.control.ComboBox; +import javafx.scene.control.ListCell; +import javafx.scene.input.KeyCode; import javafx.scene.layout.Region; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -19,27 +18,23 @@ import lombok.experimental.FieldDefaults; import java.util.List; import java.util.function.Predicate; +import java.util.function.Supplier; @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @AllArgsConstructor -public class DsStoreProviderChoiceComp extends Comp>> { +public class DataStoreProviderChoiceComp extends Comp>> { Predicate filter; Property provider; boolean staticDisplay; - private Region createDefaultNode() { - return JfxHelper.createNamedEntry( - AppI18n.get("selectType"), AppI18n.get("selectTypeDescription"), "connection_icon.svg"); - } - private List getProviders() { return DataStoreProviders.getAll().stream().filter(filter).toList(); } private Region createGraphic(DataStoreProvider provider) { if (provider == null) { - return createDefaultNode(); + return null; } var graphic = provider.getDisplayIconFileName(null); @@ -47,20 +42,40 @@ public class DsStoreProviderChoiceComp extends Comp } @Override - public CompStructure> createBase() { - var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, createDefaultNode(), v -> true); - comboBox.setAccessibleNames(dataStoreProvider -> dataStoreProvider.getDisplayName()); + public CompStructure> createBase() { + Supplier> cellFactory = () -> new ListCell<>() { + @Override + protected void updateItem(DataStoreProvider item, boolean empty) { + super.updateItem(item, empty); + setGraphic(createGraphic(item)); + setAccessibleText(item != null ? item.getDisplayName() : null); + setAccessibleHelp(item != null ? item.getDisplayDescription() : null); + } + }; + var cb = new ComboBox(); + cb.setCellFactory(param -> { + return cellFactory.get(); + }); + cb.setButtonCell(cellFactory.get()); var l = getProviders().stream() - .filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.getCreationCategory() != null || staticDisplay).toList(); - l - .forEach(comboBox::add); - if (l.size() == 1) { - provider.setValue(l.get(0)); + .filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.getCreationCategory() != null || staticDisplay) + .toList(); + l.forEach(dataStoreProvider -> cb.getItems().add(dataStoreProvider)); + if (provider.getValue() == null) { + provider.setValue(l.getFirst()); } - ComboBox cb = comboBox.build(); - cb.getStyleClass().add("data-source-type"); + cb.setValue(provider.getValue()); + provider.bind(cb.valueProperty()); cb.getStyleClass().add("choice-comp"); cb.setAccessibleText("Choose connection type"); + cb.setOnKeyPressed(event -> { + if (!event.getCode().equals(KeyCode.ENTER)) { + return; + } + + cb.show(); + event.consume(); + }); return new SimpleCompStructure<>(cb); } } diff --git a/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java b/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java index fae8ab4f2..8aef2cd53 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java +++ b/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java @@ -276,7 +276,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { var layout = new BorderPane(); layout.getStyleClass().add("store-creator"); layout.setPadding(new Insets(20)); - var providerChoice = new DsStoreProviderChoiceComp(filter, provider, staticDisplay); + var providerChoice = new DataStoreProviderChoiceComp(filter, provider, staticDisplay); if (staticDisplay) { providerChoice.apply(struc -> struc.get().setDisable(true)); } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/CharsetChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/CharsetChoiceComp.java deleted file mode 100644 index 71f1cacd6..000000000 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/CharsetChoiceComp.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.xpipe.app.fxcomps.impl; - -import io.xpipe.app.core.AppI18n; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.util.CustomComboBoxBuilder; -import io.xpipe.core.charsetter.StreamCharset; -import javafx.beans.property.Property; -import javafx.scene.control.Label; -import javafx.scene.layout.Region; - -public class CharsetChoiceComp extends SimpleComp { - - private final Property charset; - - public CharsetChoiceComp(Property charset) { - this.charset = charset; - } - - @Override - protected Region createSimple() { - var builder = new CustomComboBoxBuilder<>( - charset, - streamCharset -> { - return new Label(streamCharset.getCharset().displayName() - + (streamCharset.hasByteOrderMark() ? " (BOM)" : "")); - }, - new Label(AppI18n.get("app.none")), - null); - builder.setAccessibleNames(streamCharset -> streamCharset.getNames().get(0)); - builder.addFilter((charset, filter) -> { - return charset.getCharset().displayName().contains(filter); - }); - builder.addHeader(AppI18n.get("app.common")); - for (var e : StreamCharset.COMMON) { - builder.add(e); - } - - builder.addHeader(AppI18n.get("app.other")); - for (var e : StreamCharset.RARE) { - builder.add(e); - } - var comboBox = builder.build(); - comboBox.setVisibleRowCount(16); - return comboBox; - } -} diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/FileReferenceChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java similarity index 63% rename from app/src/main/java/io/xpipe/app/fxcomps/impl/FileReferenceChoiceComp.java rename to app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java index 0520492ed..809046757 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/FileReferenceChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java @@ -8,7 +8,6 @@ import io.xpipe.app.fxcomps.util.SimpleChangeListener; import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.core.store.FileSystemStore; import javafx.beans.property.Property; -import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.scene.layout.HBox; @@ -18,14 +17,12 @@ import org.kordamp.ikonli.javafx.FontIcon; import java.util.List; -public class FileReferenceChoiceComp extends SimpleComp { +public class ContextualFileReferenceChoiceComp extends SimpleComp { - private final boolean hideFileSystem; private final Property> fileSystem; private final Property filePath; - public FileReferenceChoiceComp(ObservableValue> fileSystem, Property filePath) { - this.hideFileSystem = true; + public ContextualFileReferenceChoiceComp(ObservableValue> fileSystem, Property filePath) { this.fileSystem = new SimpleObjectProperty<>(); SimpleChangeListener.apply(fileSystem, val -> { this.fileSystem.setValue(val); @@ -33,27 +30,15 @@ public class FileReferenceChoiceComp extends SimpleComp { this.filePath = filePath; } - public FileReferenceChoiceComp(boolean hideFileSystem, Property> fileSystem, Property filePath) { - this.hideFileSystem = hideFileSystem; - this.fileSystem = fileSystem != null ? fileSystem : new SimpleObjectProperty<>(); - this.filePath = filePath; - } - @Override protected Region createSimple() { - var fileSystemChoiceComp = - new FileSystemStoreChoiceComp(fileSystem).grow(false, true).styleClass(Styles.LEFT_PILL); - if (hideFileSystem) { - fileSystemChoiceComp.hide(new SimpleBooleanProperty(true)); - } - var fileNameComp = new TextFieldComp(filePath) .apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS)) - .styleClass(hideFileSystem ? Styles.LEFT_PILL : Styles.CENTER_PILL) + .styleClass(Styles.LEFT_PILL) .grow(false, true); var fileBrowseButton = new ButtonComp(null, new FontIcon("mdi2f-folder-open-outline"), () -> { - StandaloneFileBrowser.openSingleFile(() -> hideFileSystem ? fileSystem.getValue() : null, fileStore -> { + StandaloneFileBrowser.openSingleFile(() -> fileSystem.getValue(), fileStore -> { if (fileStore == null) { filePath.setValue(null); fileSystem.setValue(null); @@ -66,7 +51,7 @@ public class FileReferenceChoiceComp extends SimpleComp { .styleClass(Styles.RIGHT_PILL) .grow(false, true); - var layout = new HorizontalComp(List.of(fileSystemChoiceComp, fileNameComp, fileBrowseButton)) + var layout = new HorizontalComp(List.of(fileNameComp, fileBrowseButton)) .apply(struc -> struc.get().setFillHeight(true)); layout.apply(struc -> { diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/FileSystemStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/FileSystemStoreChoiceComp.java deleted file mode 100644 index 0ae3c0ed6..000000000 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/FileSystemStoreChoiceComp.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.xpipe.app.fxcomps.impl; - -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.storage.DataStorage; -import io.xpipe.app.storage.DataStoreEntry; -import io.xpipe.app.storage.DataStoreEntryRef; -import io.xpipe.app.util.CustomComboBoxBuilder; -import io.xpipe.core.store.FileSystemStore; -import javafx.beans.property.Property; -import javafx.scene.Node; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.layout.Region; - -public class FileSystemStoreChoiceComp extends SimpleComp { - - private final Property> selected; - - public FileSystemStoreChoiceComp(Property> selected) { - this.selected = selected; - } - - private static String getName(DataStoreEntryRef store) { - return store.get().getName(); - } - - private Region createGraphic(DataStoreEntryRef s) { - var provider = s.get().getProvider(); - var img = PrettyImageHelper.ofFixedSquare(provider.getDisplayIconFileName(s.getStore()), 16); - return new Label(getName(s), img.createRegion()); - } - - private Region createDisplayGraphic(DataStoreEntryRef s) { - var provider = s.get().getProvider(); - var img = PrettyImageHelper.ofFixedSquare(provider.getDisplayIconFileName(s.getStore()), 16); - return new Label(null, img.createRegion()); - } - - @Override - protected Region createSimple() { - var comboBox = new CustomComboBoxBuilder<>(selected, this::createGraphic, null, v -> true); - comboBox.setAccessibleNames(FileSystemStoreChoiceComp::getName); - comboBox.setSelectedDisplay(this::createDisplayGraphic); - DataStorage.get().getUsableEntries().stream() - .filter(e -> e.getStore() instanceof FileSystemStore) - .map(DataStoreEntry::ref) - .forEach(comboBox::add); - ComboBox cb = comboBox.build(); - cb.getStyleClass().add("choice-comp"); - cb.setMaxWidth(45); - return cb; - } -} diff --git a/app/src/main/java/io/xpipe/app/util/CustomComboBoxBuilder.java b/app/src/main/java/io/xpipe/app/util/CustomComboBoxBuilder.java deleted file mode 100644 index e5b6b4f16..000000000 --- a/app/src/main/java/io/xpipe/app/util/CustomComboBoxBuilder.java +++ /dev/null @@ -1,266 +0,0 @@ -package io.xpipe.app.util; - -import io.xpipe.app.core.AppI18n; -import io.xpipe.app.fxcomps.impl.FilterComp; -import io.xpipe.app.fxcomps.util.SimpleChangeListener; -import javafx.application.Platform; -import javafx.beans.property.Property; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.value.ObservableValue; -import javafx.collections.FXCollections; -import javafx.geometry.Orientation; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.control.ListCell; -import javafx.scene.control.Separator; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Region; -import javafx.scene.layout.VBox; - -import java.util.*; -import java.util.function.BiPredicate; -import java.util.function.Function; -import java.util.function.Predicate; - -public class CustomComboBoxBuilder { - - private final Property selected; - private final Function nodeFunction; - private ObservableValue emptyAccessibilityName = AppI18n.observable("none"); - private Function accessibleNameFunction; - private Function selectedDisplayNodeFunction; - private final Map nodeMap = new HashMap<>(); - private final Map actionsMap = new HashMap<>(); - private final List nodes = new ArrayList<>(); - private final Set disabledNodes = new HashSet<>(); - private final Node emptyNode; - private final Predicate veto; - private final Property filterString = new SimpleStringProperty(); - private final List filterable = new ArrayList<>(); - private BiPredicate filterPredicate; - private Node filterNode; - private Function unknownNode; - - public CustomComboBoxBuilder( - Property selected, Function nodeFunction, Node emptyNode, Predicate veto) { - this.selected = selected; - this.nodeFunction = nodeFunction; - this.selectedDisplayNodeFunction = nodeFunction; - this.emptyNode = emptyNode; - this.veto = veto; - } - - public void setSelectedDisplay(Function nodeFunction) { - selectedDisplayNodeFunction = nodeFunction; - } - - public void setAccessibleNames(Function function) { - accessibleNameFunction = function; - } - - public void setEmptyAccessibilityName(ObservableValue n) { - emptyAccessibilityName = n; - } - - public void addAction(Node node, Runnable run) { - nodes.add(node); - actionsMap.put(node, run); - } - - public void disable(Node node) { - disabledNodes.add(node); - } - - public void setUnknownNode(Function node) { - unknownNode = node; - } - - public Node add(T val) { - var node = nodeFunction.apply(val); - nodeMap.put(node, val); - nodes.add(node); - if (filterPredicate != null) { - filterable.add(val); - } - return node; - } - - public void addSeparator() { - var sep = new Separator(Orientation.HORIZONTAL); - nodes.add(sep); - disabledNodes.add(sep); - } - - public void addHeader(String name) { - var spacer = new Region(); - spacer.setPrefHeight(10); - var header = new Label(name); - header.setAlignment(Pos.CENTER); - var v = new VBox(spacer, header, new Separator(Orientation.HORIZONTAL)); - v.setAccessibleText(name); - v.setAlignment(Pos.CENTER); - nodes.add(v); - disabledNodes.add(v); - } - - public void addFilter(BiPredicate filterPredicate) { - this.filterPredicate = filterPredicate; - - var spacer = new Region(); - spacer.setPrefHeight(10); - var header = new FilterComp(filterString).createStructure(); - var v = new VBox(header.get()); - v.setAlignment(Pos.CENTER); - nodes.add(v); - filterNode = header.getText(); - } - - public ComboBox build() { - var cb = new ComboBox(); - cb.getItems().addAll(nodes); - - cb.setCellFactory((lv) -> { - return new Cell(); - }); - cb.setButtonCell(new SelectedCell()); - SimpleChangeListener.apply(selected, c -> { - var item = nodeMap.entrySet().stream() - .filter(e -> Objects.equals(c, e.getValue())) - .map(e -> e.getKey()) - .findAny() - .orElse(c == null || unknownNode == null ? emptyNode : unknownNode.apply(c)); - cb.setValue(item); - }); - cb.valueProperty().addListener((c, o, n) -> { - if (nodeMap.containsKey(n)) { - if (veto != null && !veto.test(nodeMap.get(n))) { - return; - } - selected.setValue(nodeMap.get(n)); - } - - if (actionsMap.containsKey(n)) { - cb.setValue(o); - actionsMap.get(n).run(); - } - }); - - if (filterPredicate != null) { - SimpleChangeListener.apply(filterString, c -> { - var filteredNodes = nodes.stream() - .filter(e -> e.equals(cb.getValue()) - || !(nodeMap.get(e) != null - && (filterable.contains(nodeMap.get(e)) - && filterString.getValue() != null - && !filterPredicate.test(nodeMap.get(e), c)))) - .toList(); - cb.setItems(FXCollections.observableList(filteredNodes)); - }); - - filterNode.sceneProperty().addListener((c, o, n) -> { - if (n != null) { - n.getWindow().focusedProperty().addListener((c2, o2, n2) -> { - Platform.runLater(() -> { - filterNode.requestFocus(); - }); - }); - } - Platform.runLater(() -> { - filterNode.requestFocus(); - }); - }); - } - - if (emptyNode != null) { - emptyNode.setAccessibleText(emptyAccessibilityName.getValue()); - } - if (accessibleNameFunction != null) { - nodes.forEach(node -> node.setAccessibleText(accessibleNameFunction.apply(nodeMap.get(node)))); - } - - return cb; - } - - private class SelectedCell extends ListCell { - - @Override - protected void updateItem(Node item, boolean empty) { - super.updateItem(item, empty); - - accessibleTextProperty().unbind(); - if (empty || item.equals(emptyNode)) { - if (emptyAccessibilityName != null) { - accessibleTextProperty().bind(emptyAccessibilityName); - } else { - setAccessibleText(null); - } - } - - if (empty) { - return; - } - - if (item.equals(emptyNode)) { - setGraphic(item); - return; - } - - // Case for dynamically created unknown nodes - if (!nodeMap.containsKey(item)) { - setGraphic(item); - // Don't expect the accessible name function to properly map this item - setAccessibleText(null); - return; - } - - var val = nodeMap.get(item); - var newNode = selectedDisplayNodeFunction.apply(val); - setGraphic(newNode); - setAccessibleText(newNode.getAccessibleText()); - } - } - - private class Cell extends ListCell { - - public Cell() { - addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { - if (!nodeMap.containsKey(getItem())) { - event.consume(); - } - }); - addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.ENTER && !nodeMap.containsKey(getItem())) { - event.consume(); - } - }); - } - - @Override - protected void updateItem(Node item, boolean empty) { - setGraphic(item); - if (getItem() == item) { - return; - } - - super.updateItem(item, empty); - if (item == null) { - return; - } - - setGraphic(item); - if (disabledNodes.contains(item)) { - this.setDisable(true); - this.setFocusTraversable(false); - // this.setPadding(Insets.EMPTY); - } else { - this.setDisable(false); - this.setFocusTraversable(true); - setAccessibleText(item.getAccessibleText()); - } - } - } -}