diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java index aa12be15..da3e5871 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java @@ -6,6 +6,7 @@ import io.xpipe.app.comp.storage.store.StoreViewState; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.IconButtonComp; import io.xpipe.app.fxcomps.impl.PrettyImageComp; +import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.core.store.DataStore; import io.xpipe.core.store.ShellStore; import javafx.application.Platform; @@ -13,6 +14,7 @@ import javafx.beans.property.*; import javafx.collections.SetChangeListener; import javafx.css.PseudoClass; import javafx.geometry.Point2D; +import javafx.scene.AccessibleRole; import javafx.scene.Node; import javafx.scene.control.TreeCell; import javafx.scene.control.TreeItem; @@ -47,7 +49,7 @@ final class BrowserBookmarkList extends SimpleComp { return new StoreCell(); }); - model.getSelected().addListener((observable, oldValue, newValue) -> { + PlatformThread.sync(model.getSelected()).addListener((observable, oldValue, newValue) -> { if (newValue == null) { view.getSelectionModel().clearSelection(); return; @@ -95,6 +97,7 @@ final class BrowserBookmarkList extends SimpleComp { private StoreCell() { disableProperty().bind(busy); + setAccessibleRole(AccessibleRole.BUTTON); setGraphic(imageView); addEventHandler(DragEvent.DRAG_OVER, mouseEvent -> { if (getItem() == null) { @@ -131,7 +134,8 @@ final class BrowserBookmarkList extends SimpleComp { }) .apply(struc -> struc.get().setPrefWidth(25)) .grow(false, true) - .styleClass("expand-button"); + .styleClass("expand-button") + .apply(struc -> struc.get().setFocusTraversable(false)); setDisclosureNode(button.createRegion()); } @@ -145,12 +149,16 @@ final class BrowserBookmarkList extends SimpleComp { // and cells are emptied on each change, leading to unnecessary changes // img.set(null); setGraphic(null); + setFocusTraversable(false); + setAccessibleText(null); } else { setText(item.getName()); img.set(item.getEntry() .getProvider() .getDisplayIconFileName(item.getEntry().getStore())); setGraphic(imageView); + setFocusTraversable(true); + setAccessibleText(item.getName() + " " + item.getEntry().getProvider().getDisplayName()); } } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java index 6d658406..471724c7 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java @@ -165,7 +165,8 @@ public class BrowserComp extends SimpleComp { // Handle selection from model model.getSelected().addListener((observable, oldValue, newValue) -> { PlatformThread.runLaterIfNeeded(() -> { - tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(newValue)); + var tab = tabs.getTabs().get(model.getOpenFileSystems().indexOf(newValue)); + tabs.getSelectionModel().select(tab); }); }); @@ -258,6 +259,7 @@ public class BrowserComp extends SimpleComp { new FancyTooltipAugment<>(new SimpleStringProperty(model.getName())).augment(label); GrowAugment.create(true, false).augment(new SimpleCompStructure<>(label)); tab.setContent(new OpenFileSystemComp(model).createSimple()); + tab.setText(model.getName()); return tab; } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java index 2875dc2d..d96c5db1 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java @@ -26,6 +26,7 @@ import javafx.collections.ListChangeListener; import javafx.css.PseudoClass; import javafx.geometry.Bounds; import javafx.geometry.Pos; +import javafx.scene.AccessibleRole; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.control.skin.TableViewSkin; @@ -100,10 +101,12 @@ final class BrowserFileListComp extends SimpleComp { modeCol.setSortable(false); var table = new TableView(); + table.setAccessibleText("Directory contents"); table.setPlaceholder(new Region()); table.getStyleClass().add(Styles.STRIPED); table.getColumns().setAll(filenameCol, sizeCol, modeCol, mtimeCol); table.getSortOrder().add(filenameCol); + table.setFocusTraversable(true); table.setSortPolicy(param -> { var comp = table.getComparator(); if (comp == null) { @@ -229,6 +232,12 @@ final class BrowserFileListComp extends SimpleComp { table.setRowFactory(param -> { TableRow row = new TableRow<>(); + row.accessibleTextProperty().bind(Bindings.createStringBinding(() -> { + return row.getItem() != null ? row.getItem().getFileName() : null; + }, row.itemProperty())); + row.focusTraversableProperty().bind(Bindings.createBooleanBinding(() -> { + return row.getItem() != null; + }, row.itemProperty())); new ContextMenuAugment<>(event -> { if (row.getItem() == null) { return event.getButton() == MouseButton.SECONDARY; @@ -405,6 +414,11 @@ final class BrowserFileListComp extends SimpleComp { private final BooleanProperty updating = new SimpleBooleanProperty(); public FilenameCell(Property editing) { + accessibleTextProperty().bind(Bindings.createStringBinding(() -> { + return getItem() != null ? getItem() : null; + }, itemProperty())); + setAccessibleRole(AccessibleRole.TEXT); + editing.addListener((observable, oldValue, newValue) -> { if (getTableRow().getItem() != null && getTableRow().getItem().equals(newValue)) { PlatformThread.runLaterIfNeeded(() -> textField.requestFocus()); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java index 6f5192bc..a34b1361 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java @@ -86,7 +86,7 @@ public class BrowserFileListCompEntry { } // Prevent dropping items onto themselves - if (item != null && BrowserClipboard.currentDragClipboard.getEntries().contains(item)) { + if (item != null && BrowserClipboard.currentDragClipboard.getEntries().contains(item.getRawFileEntry())) { return false; } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java index de050282..cece59b0 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java @@ -71,7 +71,7 @@ public class BrowserNavBar extends SimpleComp { struc.get().setPromptText("Overview of " + model.getName()); }).shortcut(new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), s -> { s.get().requestFocus(); - }); + }).accessibleText("Current path"); var graphic = Bindings.createStringBinding( () -> { @@ -87,6 +87,7 @@ public class BrowserNavBar extends SimpleComp { .createRegion(); var graphicButton = new Button(null, breadcrumbsGraphic); + graphicButton.setAccessibleText("Directory options"); graphicButton.getStyleClass().add(Styles.LEFT_PILL); graphicButton.getStyleClass().add("path-graphic-button"); new ContextMenuAugment<>( diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java index a06ee5ec..142f27f0 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java @@ -45,6 +45,7 @@ public class OpenFileSystemComp extends SimpleComp { var overview = new Button(null, new FontIcon("mdi2m-monitor")); overview.setOnAction(e -> model.cd(null)); overview.disableProperty().bind(model.getInOverview()); + overview.setAccessibleText("System overview"); var backBtn = BrowserAction.byId("back").toButton(model, List.of()); var forthBtn = BrowserAction.byId("forward").toButton(model, List.of()); @@ -56,6 +57,7 @@ public class OpenFileSystemComp extends SimpleComp { event -> event.getButton() == MouseButton.PRIMARY, () -> new BrowserContextMenu(model, null)) .augment(new SimpleCompStructure<>(menuButton)); menuButton.disableProperty().bind(model.getInOverview()); + menuButton.setAccessibleText("Directory options"); var filter = new BrowserFilterComp(model, model.getFilter()).createStructure(); Shortcuts.addShortcut(filter.toggleButton(), new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN)); diff --git a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java b/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java index f4a45a49..8ed18649 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java @@ -36,6 +36,7 @@ public interface LeafAction extends BrowserAction { b.setGraphic(graphic); } b.setMnemonicParsing(false); + b.setAccessibleText(getName(model, selected)); b.setDisable(!isActive(model, selected)); model.getCurrentPath().addListener((observable, oldValue, newValue) -> { diff --git a/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java b/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java index bdc2e24e..874227f7 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java +++ b/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java @@ -34,6 +34,7 @@ public class BigIconButton extends ButtonComp { vbox.getChildren().add(label); var b = new Button(null); + b.accessibleTextProperty().bind(getName()); b.setGraphic(vbox); b.setOnAction(e -> getListener().run()); b.getStyleClass().add("big-icon-button-comp"); diff --git a/app/src/main/java/io/xpipe/app/comp/source/DataSourceTargetChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/source/DataSourceTargetChoiceComp.java index ac33f01f..a1104700 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/DataSourceTargetChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/DataSourceTargetChoiceComp.java @@ -68,7 +68,7 @@ public class DataSourceTargetChoiceComp extends Comp( - selectedApplication, app -> createLabel(app), new Label(""), v -> true); + selectedApplication, app -> createLabel(app), dataSourceTarget -> dataSourceTarget.getName().getValue(), new Label(""), v -> true); // builder.addFilter((v, s) -> v.getName().getValue().toLowerCase().contains(s)); diff --git a/app/src/main/java/io/xpipe/app/comp/source/DsProviderChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/source/DsProviderChoiceComp.java index bbe9e7ad..1fff7398 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/DsProviderChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/DsProviderChoiceComp.java @@ -72,7 +72,7 @@ public class DsProviderChoiceComp extends Comp>> im @Override public CompStructure> createBase() { - var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, createDefaultNode(), v -> true); + var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, dataSourceProvider -> dataSourceProvider.getDisplayName(), createDefaultNode(), v -> true); comboBox.add(null); comboBox.addSeparator(); comboBox.addFilter((v, s) -> v.getDisplayName().toLowerCase().contains(s.toLowerCase())); diff --git a/app/src/main/java/io/xpipe/app/comp/source/DsStorageGroupSelector.java b/app/src/main/java/io/xpipe/app/comp/source/DsStorageGroupSelector.java index 168aff55..ff7f5892 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/DsStorageGroupSelector.java +++ b/app/src/main/java/io/xpipe/app/comp/source/DsStorageGroupSelector.java @@ -30,7 +30,7 @@ public class DsStorageGroupSelector extends SimpleComp { @Override protected ComboBox createSimple() { var comboBox = new CustomComboBoxBuilder( - selected, DsStorageGroupSelector::createGraphic, createGraphic(null), v -> true); + selected, DsStorageGroupSelector::createGraphic, dataSourceCollection -> dataSourceCollection.getName(), createGraphic(null), v -> true); DataStorage.get().getSourceCollections().stream() .filter(dataSourceCollection -> diff --git a/app/src/main/java/io/xpipe/app/comp/source/DsTypeChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/source/DsTypeChoiceComp.java index 8b2c8cb0..b48d04c0 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/DsTypeChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/DsTypeChoiceComp.java @@ -50,7 +50,7 @@ public class DsTypeChoiceComp extends Comp> { return; } - var builder = new CustomComboBoxBuilder<>(selectedType, app -> createLabel(app), new Label(""), v -> true); + var builder = new CustomComboBoxBuilder<>(selectedType, app -> createLabel(app), dataSourceType -> dataSourceType.toString(), new Label(""), v -> true); builder.add(provider.getValue().getPrimaryType()); var list = Arrays.stream(DataSourceType.values()) diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/DsStoreProviderChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/source/store/DsStoreProviderChoiceComp.java index 033f628a..e32c1462 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/DsStoreProviderChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/DsStoreProviderChoiceComp.java @@ -49,7 +49,7 @@ public class DsStoreProviderChoiceComp extends Comp @Override public CompStructure> createBase() { - var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, createDefaultNode(), v -> true); + var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, dataStoreProvider -> dataStoreProvider.getDisplayName(), createDefaultNode(), v -> true); getProviders().stream() .filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.shouldShow()) .forEach(comboBox::add); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java index 3566cc53..221f4c21 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java @@ -148,7 +148,11 @@ public class StoreEntryComp extends SimpleComp { GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid)); button.getStyleClass().add("store-entry-comp"); button.setMaxWidth(2000); - button.setFocusTraversable(false); + button.setFocusTraversable(true); + button.accessibleTextProperty().bind(Bindings.createStringBinding(() -> { + return entry.getName(); + }, entry.nameProperty())); + button.accessibleHelpProperty().bind(entry.getInformation()); button.setOnAction(event -> { event.consume(); ThreadHelper.runFailableAsync(() -> { @@ -214,6 +218,7 @@ public class StoreEntryComp extends SimpleComp { private Comp createSettingsButton() { var settingsButton = new IconButtonComp("mdomz-settings"); settingsButton.styleClass("settings"); + settingsButton.accessibleText("Settings"); settingsButton.apply(new ContextMenuAugment<>(event -> event.getButton() == MouseButton.PRIMARY, () -> StoreEntryComp.this.createContextMenu())); settingsButton.apply(GrowAugment.create(false, true)); settingsButton.apply(s -> { 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 c1d03af2..4b2a882a 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 @@ -36,6 +36,8 @@ public class StoreEntrySection extends Comp> { section.getWrapper().toggleExpanded(); }) .apply(struc -> struc.get().setPrefWidth(40)) + .focusTraversable() + .accessibleText("Expand") .disable(BindingsHelper.persist( Bindings.size(section.getChildren()).isEqualTo(0))) .grow(false, true).styleClass("expand-button"); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/Comp.java b/app/src/main/java/io/xpipe/app/fxcomps/Comp.java index d84303b1..8783c312 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/Comp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/Comp.java @@ -59,6 +59,14 @@ public abstract class Comp> { return apply(struc -> VBox.setVgrow(struc.get(), Priority.ALWAYS)); } + public Comp focusTraversable() { + return apply(struc -> struc.get().setFocusTraversable(true)); + } + + public Comp focusTraversable(boolean b) { + return apply(struc -> struc.get().setFocusTraversable(b)); + } + public Comp visible(ObservableValue o) { return apply(struc -> struc.get().visibleProperty().bind(o)); } @@ -90,6 +98,11 @@ public abstract class Comp> { return apply(struc -> struc.get().getStyleClass().add(styleClass)); } + public Comp accessibleText(String text) { + return apply(struc -> struc.get().setAccessibleText(text)); + } + + public Comp grow(boolean width, boolean height) { return apply(GrowAugment.create(width, height)); } 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 index 2395eb73..07a93f51 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/CharsetChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/CharsetChoiceComp.java @@ -24,7 +24,7 @@ public class CharsetChoiceComp extends SimpleComp { return new Label(streamCharset.getCharset().displayName() + (streamCharset.hasByteOrderMark() ? " (BOM)" : "")); }, - new Label(AppI18n.get("app.none")), + streamCharset -> streamCharset.getNames().get(0), new Label(AppI18n.get("app.none")), null); builder.addFilter((charset, filter) -> { return charset.getCharset().displayName().contains(filter); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java index 631f7b5c..6af383ee 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java @@ -59,8 +59,8 @@ public class DataStoreChoiceComp extends SimpleComp { protected Region createGraphic(T s) { var provider = DataStoreProviders.byStore(s); - var imgView = - new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName(s)), 16, 16).createRegion(); + var imgView = new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName(s)), 16, 16) + .createRegion(); var name = DataStorage.get().getUsableStores().stream() .filter(e -> e.equals(s)) @@ -77,6 +77,14 @@ public class DataStoreChoiceComp extends SimpleComp { return new Label(name, imgView); } + private String toName(DataStore store) { + if (mode == Mode.PROXY && store instanceof ShellStore && ShellStore.isLocal(store.asNeeded())) { + return AppI18n.get("none"); + } + + return XPipeDaemon.getInstance().getStoreName(store).orElse("?"); + } + @Override @SuppressWarnings("unchecked") protected Region createSimple() { @@ -88,6 +96,7 @@ public class DataStoreChoiceComp extends SimpleComp { .findFirst() .orElseThrow() .createRegion(), + t -> toName(t), new Label(AppI18n.get("none")), n -> true); comboBox.setSelectedDisplay(t -> createGraphic(t)); 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 index 88c6f95f..68e4fc46 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/FileSystemStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/FileSystemStoreChoiceComp.java @@ -60,7 +60,7 @@ public class FileSystemStoreChoiceComp extends SimpleComp { }); var comboBox = - new CustomComboBoxBuilder(fileSystemProperty, this::createGraphic, null, v -> true); + new CustomComboBoxBuilder(fileSystemProperty, this::createGraphic, store -> getName(store), null, v -> true); comboBox.setSelectedDisplay(this::createDisplayGraphic); DataStorage.get().getUsableStores().stream() .filter(e -> e instanceof FileSystemStore) diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/IconButtonComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/IconButtonComp.java index 750a7f15..7ab40a4f 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/IconButtonComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/IconButtonComp.java @@ -34,6 +34,7 @@ public class IconButtonComp extends Comp> { var button = new JFXButton(); var fi = new FontIcon(icon.getValue()); + fi.setFocusTraversable(false); icon.addListener((c, o, n) -> { fi.setIconLiteral(n); }); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/OptionsComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/OptionsComp.java index 4ee27165..b00b6f8a 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/OptionsComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/OptionsComp.java @@ -107,6 +107,8 @@ public class OptionsComp extends Comp> { } if (compRegion != null) { + compRegion.accessibleTextProperty().bind(name.textProperty()); + compRegion.accessibleHelpProperty().bind(description.textProperty()); line.getChildren().add(compRegion); } @@ -132,6 +134,7 @@ public class OptionsComp extends Comp> { line.getChildren().add(name); if (compRegion != null) { + compRegion.accessibleTextProperty().bind(name.textProperty()); compRegions.add(compRegion); line.getChildren().add(compRegion); HBox.setHgrow(compRegion, Priority.ALWAYS); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgView.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgView.java index 859d91a5..57e28d5b 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgView.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgView.java @@ -95,6 +95,7 @@ public class SvgView { wv.setPageFill(Color.TRANSPARENT); wv.getEngine().setJavaScriptEnabled(false); wv.setContextMenuEnabled(false); + wv.setFocusTraversable(false); wv.getEngine().loadContent(getHtml(svgContent.getValue())); svgContent.addListener((c, o, n) -> { diff --git a/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java b/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java index 4e3487a6..2558809c 100644 --- a/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java +++ b/app/src/main/java/io/xpipe/app/prefs/CustomFormRenderer.java @@ -11,6 +11,7 @@ import com.dlsc.preferencesfx.formsfx.view.renderer.PreferencesFxGroupRenderer; import com.dlsc.preferencesfx.util.PreferencesFxUtils; import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppI18n; +import io.xpipe.app.fxcomps.util.BindingsHelper; import javafx.geometry.Insets; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; @@ -53,7 +54,7 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer { if (nextRow > 1) { GridPane.setMargin(titleLabel, new Insets(SPACING * 3, 0, SPACING, 0)); } else { - GridPane.setMargin(titleLabel, new Insets(SPACING, 0, SPACING, 0)); + GridPane.setMargin(titleLabel, new Insets(SPACING, 0, SPACING, 0)); } } @@ -77,21 +78,37 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer { c.getFieldLabel().setMaxHeight(AppFont.getPixelSize(1)); grid.add(c.getFieldLabel(), 0, i + rowAmount, 2, 1); + var canFocus = BindingsHelper.persist(c.getNode().disabledProperty().not()); + var descriptionLabel = new Label(); descriptionLabel.setWrapText(true); - descriptionLabel.disableProperty().bind(c.getFieldLabel().disabledProperty()); - descriptionLabel.opacityProperty().bind(c.getFieldLabel().opacityProperty().multiply(0.8)); - descriptionLabel.managedProperty().bind(c.getFieldLabel().managedProperty()); - descriptionLabel.visibleProperty().bind(c.getFieldLabel().visibleProperty()); + descriptionLabel + .disableProperty() + .bind(c.getFieldLabel().disabledProperty()); + descriptionLabel + .opacityProperty() + .bind(c.getFieldLabel() + .opacityProperty() + .multiply(0.8)); + descriptionLabel + .managedProperty() + .bind(c.getFieldLabel().managedProperty()); + descriptionLabel + .visibleProperty() + .bind(c.getFieldLabel().visibleProperty()); descriptionLabel.setMaxHeight(USE_PREF_SIZE); if (AppI18n.getInstance().containsKey(descriptionKey)) { rowAmount++; descriptionLabel.textProperty().bind(AppI18n.observable(descriptionKey)); + descriptionLabel.focusTraversableProperty().bind(canFocus); grid.add(descriptionLabel, 0, i + rowAmount, 2, 1); } rowAmount++; - grid.add(c.getNode(), 0, i + rowAmount, 1, 1); + + var node = c.getNode(); + c.getFieldLabel().focusTraversableProperty().bind(canFocus); + grid.add(node, 0, i + rowAmount, 1, 1); if (i == elements.size() - 1) { // additional styling for the last setting @@ -101,7 +118,7 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer { var offset = preferencesGroup.getTitle() != null ? 15 : 0; GridPane.setMargin(descriptionLabel, new Insets(SPACING, 0, 0, offset)); - GridPane.setMargin(c.getNode(), new Insets(SPACING, 0, 0, offset)); + GridPane.setMargin(node, new Insets(SPACING, 0, 0, offset)); if (!((i == 0) && (nextRow > 0))) { GridPane.setMargin(c.getFieldLabel(), new Insets(SPACING * 3, 0, 0, offset)); @@ -110,7 +127,7 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer { } c.getFieldLabel().getStyleClass().add(styleClass.toString() + "-label"); - c.getNode().getStyleClass().add(styleClass.toString() + "-node"); + node.getStyleClass().add(styleClass.toString() + "-node"); } if (element instanceof NodeElement nodeElement) { diff --git a/app/src/main/java/io/xpipe/app/util/CustomComboBoxBuilder.java b/app/src/main/java/io/xpipe/app/util/CustomComboBoxBuilder.java index e0acc404..cd0d2b26 100644 --- a/app/src/main/java/io/xpipe/app/util/CustomComboBoxBuilder.java +++ b/app/src/main/java/io/xpipe/app/util/CustomComboBoxBuilder.java @@ -3,6 +3,7 @@ package io.xpipe.app.util; import io.xpipe.app.fxcomps.impl.FilterComp; import io.xpipe.app.fxcomps.util.SimpleChangeListener; import javafx.application.Platform; +import javafx.beans.binding.Bindings; import javafx.beans.property.Property; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; @@ -25,6 +26,7 @@ public class CustomComboBoxBuilder { private final Property selected; private final Function nodeFunction; + private final Function accessibleNameFunction; private Function selectedDisplayNodeFunction; private final Map nodeMap = new HashMap<>(); private final Map actionsMap = new HashMap<>(); @@ -39,10 +41,11 @@ public class CustomComboBoxBuilder { private Function unknownNode; public CustomComboBoxBuilder( - Property selected, Function nodeFunction, Node emptyNode, Predicate veto) { + Property selected, Function nodeFunction, Function accessibleNameFunction, Node emptyNode, Predicate veto) { this.selected = selected; this.nodeFunction = nodeFunction; this.selectedDisplayNodeFunction = nodeFunction; + this.accessibleNameFunction = accessibleNameFunction; this.emptyNode = emptyNode; this.veto = veto; } @@ -66,6 +69,7 @@ public class CustomComboBoxBuilder { public Node add(T val) { var node = nodeFunction.apply(val); + node.setAccessibleText(accessibleNameFunction.apply(val)); nodeMap.put(node, val); nodes.add(node); if (filterPredicate != null) { @@ -86,6 +90,7 @@ public class CustomComboBoxBuilder { 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); @@ -105,6 +110,9 @@ public class CustomComboBoxBuilder { public ComboBox build() { var cb = new ComboBox(); + cb.accessibleTextProperty().bind(Bindings.createStringBinding(() -> { + return selected.getValue() != null ? accessibleNameFunction.apply(selected.getValue()) : null; + }, selected)); cb.getItems().addAll(nodes); cb.setCellFactory((lv) -> { return new Cell();