diff --git a/src/main/java/io/github/dsheirer/gui/control/AccessibilityUtil.java b/src/main/java/io/github/dsheirer/gui/control/AccessibilityUtil.java new file mode 100644 index 00000000..9d895bd1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/control/AccessibilityUtil.java @@ -0,0 +1,84 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 Dennis Sheirer + * + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.gui.control; + +import javafx.collections.ListChangeListener; +import javafx.scene.control.Label; +import javafx.scene.control.ToggleButton; +import org.controlsfx.control.SegmentedButton; + +/** + * Screen reader (accessibility) utilities for JavaFX controls. + */ +public final class AccessibilityUtil +{ + private AccessibilityUtil() + { + //Not instantiable + } + + /** + * Provides an accessible name for each toggle button in a segmented button group, combining the group label with + * the button text (e.g. "Channel Bandwidth: 12.5 kHz"). + * + * A segmented button is a container, so a label can't identify it via Label.setLabelFor() in a way that screen + * readers announce: each toggle button receives focus individually and would otherwise be announced by its own text + * only, without the context of the group. Buttons added to the group later are also named. + * + * @param groupLabel that visually identifies the segmented button group + * @param segmentedButton containing the toggle buttons + */ + public static void labelGroup(Label groupLabel, SegmentedButton segmentedButton) + { + String groupName = groupLabel.getText() == null ? "" : groupLabel.getText().trim(); + + //Remove a trailing colon so that the name doesn't announce "colon colon" + if(groupName.endsWith(":")) + { + groupName = groupName.substring(0, groupName.length() - 1).trim(); + } + + final String name = groupName; + + for(ToggleButton button: segmentedButton.getButtons()) + { + setGroupName(name, button); + } + + segmentedButton.getButtons().addListener((ListChangeListener)change -> { + while(change.next()) + { + for(ToggleButton added: change.getAddedSubList()) + { + setGroupName(name, added); + } + } + }); + } + + /** + * Sets the accessible text for the button as the group name and button text. + */ + private static void setGroupName(String groupName, ToggleButton button) + { + String text = button.getText() == null ? "" : button.getText(); + button.setAccessibleText(groupName.isEmpty() ? text : groupName + ": " + text); + } +} diff --git a/src/main/java/io/github/dsheirer/gui/control/AccessibleToggleSwitch.java b/src/main/java/io/github/dsheirer/gui/control/AccessibleToggleSwitch.java new file mode 100644 index 00000000..805cb6dc --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/control/AccessibleToggleSwitch.java @@ -0,0 +1,80 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 Dennis Sheirer + * + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.gui.control; + +import javafx.scene.AccessibleAction; +import javafx.scene.AccessibleAttribute; +import javafx.scene.AccessibleRole; +import org.controlsfx.control.ToggleSwitch; + +/** + * ControlsFX toggle switch that exposes its role and on/off state to screen readers. + * + * The ControlsFX toggle switch doesn't provide an accessible role or selected state, so assistive technology sees it + * as a generic node with no state. This version reports itself as a toggle button, reports the selected state, and + * supports the accessible fire (toggle) action. The accessible name comes from the switch text, or from a label that + * identifies this switch via Label.setLabelFor(). + */ +public class AccessibleToggleSwitch extends ToggleSwitch +{ + /** + * Constructs an instance with no text. Use Label.setLabelFor() or setAccessibleText() to provide an accessible name. + */ + public AccessibleToggleSwitch() + { + this(null); + } + + /** + * Constructs an instance + * @param text to display alongside the switch, also used as the accessible name. + */ + public AccessibleToggleSwitch(String text) + { + super(text); + setAccessibleRole(AccessibleRole.TOGGLE_BUTTON); + selectedProperty().addListener((observable, oldValue, newValue) -> + notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTED)); + } + + @Override + public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) + { + if(attribute == AccessibleAttribute.SELECTED) + { + return isSelected(); + } + + return super.queryAccessibleAttribute(attribute, parameters); + } + + @Override + public void executeAccessibleAction(AccessibleAction action, Object... parameters) + { + if(action == AccessibleAction.FIRE) + { + fire(); + } + else + { + super.executeAccessibleAction(action, parameters); + } + } +} diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasBulkEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasBulkEditor.java index b152d121..93377acb 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasBulkEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasBulkEditor.java @@ -21,6 +21,7 @@ package io.github.dsheirer.gui.playlist.alias; import com.google.common.collect.Ordering; import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.playlist.Editor; import io.github.dsheirer.icon.Icon; import io.github.dsheirer.playlist.PlaylistManager; @@ -100,6 +101,7 @@ public class AliasBulkEditor extends Editor> gridPane.getChildren().add(separator); Label colorLabel = new Label("Color"); + colorLabel.setLabelFor(getColorPicker()); GridPane.setHalignment(colorLabel, HPos.RIGHT); GridPane.setConstraints(colorLabel, 0, ++row); gridPane.getChildren().add(colorLabel); @@ -114,6 +116,7 @@ public class AliasBulkEditor extends Editor> gridPane.getChildren().add(getResetColorButton()); Label iconLabel = new Label("Icon"); + iconLabel.setLabelFor(getIconNodeComboBox()); GridPane.setHalignment(iconLabel, HPos.RIGHT); GridPane.setConstraints(iconLabel, 0, ++row); gridPane.getChildren().add(iconLabel); @@ -125,6 +128,7 @@ public class AliasBulkEditor extends Editor> gridPane.getChildren().add(getApplyIconButton()); Label listenLabel = new Label("Listen"); + listenLabel.setLabelFor(getMonitorAudioToggleSwitch()); GridPane.setHalignment(listenLabel, HPos.RIGHT); GridPane.setConstraints(listenLabel, 0, ++row); gridPane.getChildren().add(listenLabel); @@ -133,6 +137,7 @@ public class AliasBulkEditor extends Editor> gridPane.getChildren().add(getMonitorAudioToggleSwitch()); Label priorityLabel = new Label("Priority"); + priorityLabel.setLabelFor(getMonitorPriorityComboBox()); GridPane.setHalignment(priorityLabel, HPos.RIGHT); GridPane.setConstraints(priorityLabel, 2, row); gridPane.getChildren().add(priorityLabel); @@ -144,6 +149,7 @@ public class AliasBulkEditor extends Editor> gridPane.getChildren().add(getApplyMonitorButton()); Label recordLabel = new Label("Record"); + recordLabel.setLabelFor(getRecordToggleSwitch()); GridPane.setHalignment(recordLabel, HPos.RIGHT); GridPane.setConstraints(recordLabel, 0, ++row); gridPane.getChildren().add(recordLabel); @@ -235,6 +241,7 @@ public class AliasBulkEditor extends Editor> if(mApplyColorButton == null) { mApplyColorButton = new Button("Apply"); + mApplyColorButton.setAccessibleText("Apply color to selected aliases"); mApplyColorButton.setOnAction(event -> { startChange(); @@ -258,6 +265,7 @@ public class AliasBulkEditor extends Editor> if(mResetColorButton == null) { mResetColorButton = new Button("Reset Color"); + mResetColorButton.setAccessibleText("Reset color for selected aliases"); mResetColorButton.setOnAction(event -> { startChange(); @@ -297,6 +305,7 @@ public class AliasBulkEditor extends Editor> if(mApplyIconButton == null) { mApplyIconButton = new Button("Apply"); + mApplyIconButton.setAccessibleText("Apply icon to selected aliases"); mApplyIconButton.setDisable(true); mApplyIconButton.setOnAction(new EventHandler() { @@ -327,7 +336,7 @@ public class AliasBulkEditor extends Editor> { if(mMonitorAudioToggleSwitch == null) { - mMonitorAudioToggleSwitch = new ToggleSwitch(); + mMonitorAudioToggleSwitch = new AccessibleToggleSwitch(); mMonitorAudioToggleSwitch.setSelected(true); mMonitorAudioToggleSwitch.selectedProperty() .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); @@ -340,7 +349,7 @@ public class AliasBulkEditor extends Editor> { if(mRecordToggleSwitch == null) { - mRecordToggleSwitch = new ToggleSwitch(); + mRecordToggleSwitch = new AccessibleToggleSwitch(); mRecordToggleSwitch.setSelected(false); mRecordToggleSwitch.selectedProperty() .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); @@ -374,6 +383,7 @@ public class AliasBulkEditor extends Editor> if(mApplyMonitorButton == null) { mApplyMonitorButton = new Button("Apply"); + mApplyMonitorButton.setAccessibleText("Apply listen and priority to selected aliases"); mApplyMonitorButton.setOnAction(new EventHandler() { @Override @@ -414,6 +424,7 @@ public class AliasBulkEditor extends Editor> if(mApplyRecordButton == null) { mApplyRecordButton = new Button("Apply"); + mApplyRecordButton.setAccessibleText("Apply record to selected aliases"); mApplyRecordButton.setOnAction(event -> { startChange(); boolean record = getRecordToggleSwitch().isSelected(); @@ -456,12 +467,15 @@ public class AliasBulkEditor extends Editor> { setText(null); setGraphic(null); + setAccessibleText(null); } else { textLabel.setText(item.getName()); iconLabel.setGraphic(new ImageView(item.getFxImage())); setGraphic(gridPane); + //The name is shown in a nested label, so expose it as the cell's accessible text + setAccessibleText(item.getName()); } } }; diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java index 899bde0f..f2e1924b 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java @@ -273,7 +273,9 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef Label listLabel = new Label("Alias List"); + listLabel.setLabelFor(getAliasListNameComboBox()); Label searchLabel = new Label("Search"); + searchLabel.setLabelFor(getSearchField()); searchLabel.setAlignment(Pos.CENTER_RIGHT); HBox searchBox = new HBox(); @@ -451,6 +453,7 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef if(mAliasTableView == null) { mAliasTableView = new TableView<>(); + mAliasTableView.setAccessibleText("Aliases"); TableColumn nameColumn = new TableColumn(); nameColumn.setText("Alias"); @@ -751,15 +754,18 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef UIManager.getColor("TextField.background")); rectangle.setFill(ColorUtil.fromInteger(readable.equals(color) ? alias.getColor() : readable.getRGB())); + setAccessibleText(String.format("#%06X", alias.getColor() & 0xFFFFFF)); } else { rectangle.setVisible(false); + setAccessibleText(null); } } else { rectangle.setVisible(false); + setAccessibleText(null); } } }; @@ -798,13 +804,15 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef { super.updateItem(item, empty); - if(!empty && getTableRow() != null) + if(!empty && getTableRow() != null && item != null) { iconNode.setVisible(item); + setAccessibleText(item ? "Yes" : "No"); } else { iconNode.setVisible(false); + setAccessibleText(null); } } }; @@ -919,6 +927,7 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef { super.updateItem(item, empty); setAlignment(Pos.CENTER); + setAccessibleText(null); if(empty) { @@ -937,6 +946,7 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef if(icon != null && icon.getFxImage() != null) { setGraphic(new ImageView(icon.getFxImage())); + setAccessibleText(icon.getName()); } else { diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java index 5cd2ceb1..2515e614 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java @@ -57,6 +57,7 @@ import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange; import io.github.dsheirer.alias.id.tone.TonesID; import io.github.dsheirer.audio.broadcast.ConfiguredBroadcast; import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.IntegerFormatter; import io.github.dsheirer.gui.playlist.Editor; import io.github.dsheirer.gui.playlist.alias.action.ActionEditor; @@ -574,6 +575,7 @@ public class AliasItemEditor extends Editor { mActionsList = new ListView<>(FXCollections.observableArrayList(AliasAction.extractor())); mActionsList.setPrefHeight(75); + mActionsList.setAccessibleText("Alias actions"); mActionsList.setDisable(true); mActionsList.getSelectionModel().selectedItemProperty() .addListener((observable, oldValue, newValue) -> setAction(newValue)); @@ -686,6 +688,7 @@ public class AliasItemEditor extends Editor mIdentifiersList = new ListView<>(FXCollections.observableArrayList(AliasID.extractor())); mIdentifiersList.setDisable(true); mIdentifiersList.setPrefHeight(75); + mIdentifiersList.setAccessibleText("Alias identifiers"); mIdentifiersList.getSelectionModel().selectedItemProperty() .addListener((observable, oldValue, newValue) -> setIdentifier(newValue)); mIdentifiersList.setCellFactory(param -> new AliasIdentifierCell()); @@ -846,13 +849,17 @@ public class AliasItemEditor extends Editor buttonBox.setSpacing(5); buttonBox.getChildren().addAll(new Label(" "), getAddStreamButton(), getRemoveStreamButton()); + Label availableLabel = new Label("Available"); + getAvailableStreamsView().setAccessibleText("Available streams"); VBox availableBox = new VBox(); VBox.setVgrow(getAvailableStreamsView(), Priority.ALWAYS); - availableBox.getChildren().addAll(new Label("Available"), getAvailableStreamsView()); + availableBox.getChildren().addAll(availableLabel, getAvailableStreamsView()); + Label selectedLabel = new Label("Selected"); + getSelectedStreamsView().setAccessibleText("Selected streams"); VBox selectedBox = new VBox(); VBox.setVgrow(getSelectedStreamsView(), Priority.ALWAYS); - selectedBox.getChildren().addAll(new Label("Selected"), getSelectedStreamsView()); + selectedBox.getChildren().addAll(selectedLabel, getSelectedStreamsView()); HBox hbox = new HBox(); hbox.setSpacing(10); @@ -864,6 +871,7 @@ public class AliasItemEditor extends Editor vbox.setSpacing(10); VBox.setVgrow(hbox, Priority.ALWAYS); Label label = new Label("Stream this Alias as Talkgroup:"); + label.setLabelFor(getStreamAsTalkgroupField()); HBox streamAsHBox = new HBox(); streamAsHBox.setAlignment(Pos.CENTER_LEFT); streamAsHBox.setSpacing(10); @@ -964,6 +972,8 @@ public class AliasItemEditor extends Editor .or(Bindings.isNull(getAvailableStreamsView().getSelectionModel().selectedItemProperty()))); mAddStreamButton.setMaxWidth(Double.MAX_VALUE); mAddStreamButton.setGraphic(new IconNode(FontAwesome.ANGLE_RIGHT)); + mAddStreamButton.setAccessibleText("Add selected stream"); + mAddStreamButton.setTooltip(new Tooltip("Add selected stream")); mAddStreamButton.setAlignment(Pos.CENTER); mAddStreamButton.setOnAction(event -> { String stream = getAvailableStreamsView().getSelectionModel().getSelectedItem(); @@ -990,6 +1000,8 @@ public class AliasItemEditor extends Editor .or(Bindings.isNull(getSelectedStreamsView().getSelectionModel().selectedItemProperty()))); mRemoveStreamButton.setMaxWidth(Double.MAX_VALUE); mRemoveStreamButton.setGraphic(new IconNode(FontAwesome.ANGLE_LEFT)); + mRemoveStreamButton.setAccessibleText("Remove selected stream"); + mRemoveStreamButton.setTooltip(new Tooltip("Remove selected stream")); mRemoveStreamButton.setAlignment(Pos.CENTER); mRemoveStreamButton.setOnAction(event -> { BroadcastChannel broadcastChannel = getSelectedStreamsView().getSelectionModel().getSelectedItem(); @@ -1050,6 +1062,7 @@ public class AliasItemEditor extends Editor int row = 0; Label nameLabel = new Label("Alias"); + nameLabel.setLabelFor(getNameField()); GridPane.setHalignment(nameLabel, HPos.RIGHT); GridPane.setConstraints(nameLabel, 0, row); mTextFieldPane.getChildren().add(nameLabel); @@ -1058,6 +1071,7 @@ public class AliasItemEditor extends Editor mTextFieldPane.getChildren().add(getNameField()); Label monitorAudioLabel = new Label("Listen"); + monitorAudioLabel.setLabelFor(getMonitorAudioToggleSwitch()); GridPane.setHalignment(monitorAudioLabel, HPos.RIGHT); GridPane.setConstraints(monitorAudioLabel, 2, row); mTextFieldPane.getChildren().add(monitorAudioLabel); @@ -1065,6 +1079,7 @@ public class AliasItemEditor extends Editor mTextFieldPane.getChildren().add(getMonitorAudioToggleSwitch()); Label monitorPriorityLabel = new Label("Priority"); + monitorPriorityLabel.setLabelFor(getMonitorPriorityComboBox()); GridPane.setHalignment(monitorPriorityLabel, HPos.RIGHT); GridPane.setConstraints(monitorPriorityLabel, 4, row); mTextFieldPane.getChildren().add(monitorPriorityLabel); @@ -1072,6 +1087,7 @@ public class AliasItemEditor extends Editor mTextFieldPane.getChildren().add(getMonitorPriorityComboBox()); Label colorLabel = new Label("Color"); + colorLabel.setLabelFor(getColorPicker()); GridPane.setHalignment(colorLabel, HPos.RIGHT); GridPane.setConstraints(colorLabel, 6, row); mTextFieldPane.getChildren().add(colorLabel); @@ -1079,6 +1095,7 @@ public class AliasItemEditor extends Editor mTextFieldPane.getChildren().add(getColorPicker()); Label groupLabel = new Label("Group"); + groupLabel.setLabelFor(getGroupField()); GridPane.setHalignment(groupLabel, HPos.RIGHT); GridPane.setConstraints(groupLabel, 0, ++row); mTextFieldPane.getChildren().add(groupLabel); @@ -1087,6 +1104,7 @@ public class AliasItemEditor extends Editor mTextFieldPane.getChildren().add(getGroupField()); Label recordAudioLabel = new Label("Record"); + recordAudioLabel.setLabelFor(getRecordAudioToggleSwitch()); GridPane.setHalignment(recordAudioLabel, HPos.RIGHT); GridPane.setConstraints(recordAudioLabel, 2, row); mTextFieldPane.getChildren().add(recordAudioLabel); @@ -1094,6 +1112,7 @@ public class AliasItemEditor extends Editor mTextFieldPane.getChildren().add(getRecordAudioToggleSwitch()); Label iconLabel = new Label("Icon"); + iconLabel.setLabelFor(getIconNodeComboBox()); GridPane.setHalignment(iconLabel, HPos.RIGHT); GridPane.setConstraints(iconLabel, 4, row); mTextFieldPane.getChildren().add(iconLabel); @@ -1108,7 +1127,7 @@ public class AliasItemEditor extends Editor { if(mMonitorAudioToggleSwitch == null) { - mMonitorAudioToggleSwitch = new ToggleSwitch(); + mMonitorAudioToggleSwitch = new AccessibleToggleSwitch(); mMonitorAudioToggleSwitch.setDisable(true); mMonitorAudioToggleSwitch.selectedProperty() .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); @@ -1141,7 +1160,7 @@ public class AliasItemEditor extends Editor { if(mRecordAudioToggleSwitch == null) { - mRecordAudioToggleSwitch = new ToggleSwitch(); + mRecordAudioToggleSwitch = new AccessibleToggleSwitch(); mRecordAudioToggleSwitch.setDisable(true); mRecordAudioToggleSwitch.selectedProperty() .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); @@ -1820,12 +1839,15 @@ public class AliasItemEditor extends Editor { setText(null); setGraphic(null); + setAccessibleText(null); } else { textLabel.setText(item.getName()); iconLabel.setGraphic(new ImageView(item.getFxImage())); setGraphic(gridPane); + //The name is shown in a nested label, so expose it as the cell's accessible text + setAccessibleText(item.getName()); } } }; diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByIdentifierEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByIdentifierEditor.java index 04d7553e..46adf598 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByIdentifierEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByIdentifierEditor.java @@ -97,6 +97,7 @@ public class AliasViewByIdentifierEditor extends VBox gridPane.setHgap(10); Label aliasListLabel = new Label("Alias List"); + aliasListLabel.setLabelFor(getAliasListNameComboBox()); GridPane.setHalignment(aliasListLabel, HPos.RIGHT); GridPane.setConstraints(aliasListLabel, 0, 0); gridPane.getChildren().add(aliasListLabel); @@ -106,6 +107,7 @@ public class AliasViewByIdentifierEditor extends VBox gridPane.getChildren().add(getAliasListNameComboBox()); Label identifierLabel = new Label("Identifier Type"); + identifierLabel.setLabelFor(getAliasIDTypeComboBox()); GridPane.setHalignment(identifierLabel, HPos.RIGHT); GridPane.setConstraints(identifierLabel, 0, 1); gridPane.getChildren().add(identifierLabel); @@ -210,6 +212,7 @@ public class AliasViewByIdentifierEditor extends VBox if(mAliasAndIdentifierTableView == null) { mAliasAndIdentifierTableView = new TableView<>(FXCollections.observableArrayList(AliasAndIdentifier.extractor())); + mAliasAndIdentifierTableView.setAccessibleText("Aliases by identifier"); mAliasAndIdentifierTableView.setPlaceholder(new Label("No aliases or identifiers available")); mIdentifierColumn = new TableColumn<>("Identifier"); mIdentifierColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getAliasIdentifier())); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByRecordingEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByRecordingEditor.java index fd1dce41..a7ce7c93 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByRecordingEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByRecordingEditor.java @@ -34,6 +34,7 @@ import javafx.scene.control.SelectionMode; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; @@ -157,12 +158,14 @@ public class AliasViewByRecordingEditor extends VBox aliasListBox.setAlignment(Pos.CENTER); aliasListBox.setSpacing(5); Label aliasListLabel = new Label("Alias List"); + aliasListLabel.setLabelFor(getAliasListNameComboBox()); aliasListBox.getChildren().addAll(aliasListLabel, getAliasListNameComboBox()); HBox searchBox = new HBox(); searchBox.setAlignment(Pos.CENTER); searchBox.setSpacing(5); Label searchLabel = new Label("Search"); + searchLabel.setLabelFor(getSearchField()); searchBox.getChildren().addAll(searchLabel, getSearchField()); mTopBox.getChildren().addAll(aliasListBox, searchBox); @@ -228,6 +231,7 @@ public class AliasViewByRecordingEditor extends VBox if(mNoRecordAliasTableView == null) { mNoRecordAliasTableView = new TableView<>(); + mNoRecordAliasTableView.setAccessibleText("Don't record audio aliases"); mNoRecordAliasTableView.setMaxHeight(Double.MAX_VALUE); mNoRecordAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); @@ -266,6 +270,7 @@ public class AliasViewByRecordingEditor extends VBox if(mRecordAliasTableView == null) { mRecordAliasTableView = new TableView<>(); + mRecordAliasTableView.setAccessibleText("Record audio aliases"); mRecordAliasTableView.setMaxHeight(Double.MAX_VALUE); mRecordAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); @@ -307,6 +312,8 @@ public class AliasViewByRecordingEditor extends VBox mAddButton.setDisable(true); mAddButton.setMaxWidth(Double.MAX_VALUE); mAddButton.setGraphic(new IconNode(FontAwesome.ANGLE_RIGHT)); + mAddButton.setAccessibleText("Record audio for selected alias"); + mAddButton.setTooltip(new Tooltip("Record audio for selected alias")); mAddButton.setAlignment(Pos.CENTER); mAddButton.setOnAction(event -> { Alias selectedAlias = getNoRecordAliasTableView().getSelectionModel().getSelectedItem(); @@ -330,6 +337,8 @@ public class AliasViewByRecordingEditor extends VBox mAddAllButton.setDisable(true); mAddAllButton.setMaxWidth(Double.MAX_VALUE); mAddAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_RIGHT)); + mAddAllButton.setAccessibleText("Record audio for all selected aliases"); + mAddAllButton.setTooltip(new Tooltip("Record audio for all selected aliases")); mAddAllButton.setAlignment(Pos.CENTER); mAddAllButton.setOnAction(event -> { List selectedAliases = @@ -358,6 +367,8 @@ public class AliasViewByRecordingEditor extends VBox mRemoveButton.setDisable(true); mRemoveButton.setMaxWidth(Double.MAX_VALUE); mRemoveButton.setGraphic(new IconNode(FontAwesome.ANGLE_LEFT)); + mRemoveButton.setAccessibleText("Don't record audio for selected alias"); + mRemoveButton.setTooltip(new Tooltip("Don't record audio for selected alias")); mRemoveButton.setAlignment(Pos.CENTER); mRemoveButton.setOnAction(event -> { Alias selectedAlias = getRecordAliasTableView().getSelectionModel().getSelectedItem(); @@ -381,6 +392,8 @@ public class AliasViewByRecordingEditor extends VBox mRemoveAllButton.setDisable(true); mRemoveAllButton.setMaxWidth(Double.MAX_VALUE); mRemoveAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_LEFT)); + mRemoveAllButton.setAccessibleText("Don't record audio for all selected aliases"); + mRemoveAllButton.setTooltip(new Tooltip("Don't record audio for all selected aliases")); mRemoveAllButton.setAlignment(Pos.CENTER); mRemoveAllButton.setOnAction(event -> { List selectedAliases = diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java index def7a7d8..2557bc83 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java @@ -66,6 +66,7 @@ public class BeepEditor extends ActionEditor int row = 0; Label intervalLabel = new Label(("Beep")); + intervalLabel.setLabelFor(getIntervalComboBox()); GridPane.setHalignment(intervalLabel, HPos.RIGHT); GridPane.setConstraints(intervalLabel, 0, row); gridPane.getChildren().add(intervalLabel); @@ -80,6 +81,7 @@ public class BeepEditor extends ActionEditor GridPane.setConstraints(getSecondsLabel(), 3, row); GridPane.setHgrow(getSecondsLabel(), Priority.ALWAYS); gridPane.getChildren().add(getSecondsLabel()); + getSecondsLabel().setLabelFor(getPeriodTextField()); GridPane.setConstraints(getTestButton(), 4, row); gridPane.getChildren().add(getTestButton()); @@ -155,6 +157,7 @@ public class BeepEditor extends ActionEditor if(mTestButton == null) { mTestButton = new Button("Test"); + mTestButton.setAccessibleText("Test beep"); mTestButton.setMaxWidth(Double.MAX_VALUE); mTestButton.setOnAction(event -> test()); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java index e88452ce..dd9120a0 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java @@ -71,6 +71,7 @@ public class ClipEditor extends ActionEditor int row = 0; Label fileLabel = new Label("Audio File"); + fileLabel.setLabelFor(getFilePath()); GridPane.setHalignment(fileLabel, HPos.RIGHT); GridPane.setConstraints(fileLabel, 0, row); gridPane.getChildren().add(fileLabel); @@ -83,6 +84,7 @@ public class ClipEditor extends ActionEditor gridPane.getChildren().add(getFileChooser()); Label intervalLabel = new Label(("Play Audio Clip")); + intervalLabel.setLabelFor(getIntervalComboBox()); GridPane.setHalignment(intervalLabel, HPos.RIGHT); GridPane.setConstraints(intervalLabel, 0, ++row); gridPane.getChildren().add(intervalLabel); @@ -97,6 +99,7 @@ public class ClipEditor extends ActionEditor GridPane.setConstraints(getSecondsLabel(), 3, row); GridPane.setHgrow(getSecondsLabel(), Priority.ALWAYS); gridPane.getChildren().add(getSecondsLabel()); + getSecondsLabel().setLabelFor(getPeriodTextField()); GridPane.setConstraints(getTestButton(), 4, row); gridPane.getChildren().add(getTestButton()); @@ -223,6 +226,7 @@ public class ClipEditor extends ActionEditor if(mTestButton == null) { mTestButton = new Button("Test"); + mTestButton.setAccessibleText("Test audio clip"); mTestButton.setMaxWidth(Double.MAX_VALUE); mTestButton.setDisable(true); mTestButton.setOnAction(event -> test()); @@ -236,6 +240,7 @@ public class ClipEditor extends ActionEditor if(mFileChooser == null) { mFileChooser = new Button(("Select ...")); + mFileChooser.setAccessibleText("Select audio file"); mFileChooser.setMaxWidth(Double.MAX_VALUE); mFileChooser.setDisable(true); mFileChooser.setOnAction(event -> { @@ -278,7 +283,8 @@ public class ClipEditor extends ActionEditor if(mFilePath == null) { mFilePath = new TextField(PLEASE_SELECT_A_FILE); - mFilePath.setDisable(true); + //Read-only rather than disabled so that the selected file remains focusable for screen readers + mFilePath.setEditable(false); mFilePath.setMaxWidth(Double.MAX_VALUE); mFilePath.textProperty().addListener((observable, oldValue, newValue) -> { if(newValue != null && !newValue.isEmpty() && !newValue.contentEquals(PLEASE_SELECT_A_FILE)) diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java index d5efc61b..7a07760e 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java @@ -71,6 +71,7 @@ public class ScriptEditor extends ActionEditor int row = 0; Label fileLabel = new Label("Script File"); + fileLabel.setLabelFor(getFilePath()); GridPane.setHalignment(fileLabel, HPos.RIGHT); GridPane.setConstraints(fileLabel, 0, row); gridPane.getChildren().add(fileLabel); @@ -83,6 +84,7 @@ public class ScriptEditor extends ActionEditor gridPane.getChildren().add(getFileChooser()); Label intervalLabel = new Label(("Run Script")); + intervalLabel.setLabelFor(getIntervalComboBox()); GridPane.setHalignment(intervalLabel, HPos.RIGHT); GridPane.setConstraints(intervalLabel, 0, ++row); gridPane.getChildren().add(intervalLabel); @@ -97,6 +99,7 @@ public class ScriptEditor extends ActionEditor GridPane.setConstraints(getSecondsLabel(), 3, row); GridPane.setHgrow(getSecondsLabel(), Priority.ALWAYS); gridPane.getChildren().add(getSecondsLabel()); + getSecondsLabel().setLabelFor(getPeriodTextField()); GridPane.setConstraints(getTestButton(), 4, row); gridPane.getChildren().add(getTestButton()); @@ -223,6 +226,7 @@ public class ScriptEditor extends ActionEditor if(mTestButton == null) { mTestButton = new Button("Test"); + mTestButton.setAccessibleText("Test script"); mTestButton.setMaxWidth(Double.MAX_VALUE); mTestButton.setDisable(true); mTestButton.setOnAction(event -> test()); @@ -236,6 +240,7 @@ public class ScriptEditor extends ActionEditor if(mFileChooser == null) { mFileChooser = new Button(("Select ...")); + mFileChooser.setAccessibleText("Select script file"); mFileChooser.setMaxWidth(Double.MAX_VALUE); mFileChooser.setDisable(true); mFileChooser.setOnAction(event -> { @@ -278,7 +283,8 @@ public class ScriptEditor extends ActionEditor if(mFilePath == null) { mFilePath = new TextField(PLEASE_SELECT_A_FILE); - mFilePath.setDisable(true); + //Read-only rather than disabled so that the selected file remains focusable for screen readers + mFilePath.setEditable(false); mFilePath.setMaxWidth(Double.MAX_VALUE); mFilePath.textProperty().addListener((observable, oldValue, newValue) -> { if(newValue != null && !newValue.isEmpty() && !newValue.contentEquals(PLEASE_SELECT_A_FILE)) diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/DcsEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/DcsEditor.java index 48a0e10a..70167150 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/DcsEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/DcsEditor.java @@ -44,6 +44,7 @@ public class DcsEditor extends IdentifierEditor gridPane.setHgap(5); Label typeLabel = new Label("DCS Code"); + typeLabel.setLabelFor(getDCSCodeComboBox()); GridPane.setConstraints(typeLabel, 0, 0); gridPane.getChildren().add(typeLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/EsnEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/EsnEditor.java index e5487a75..baf77a9f 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/EsnEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/EsnEditor.java @@ -54,6 +54,7 @@ public class EsnEditor extends IdentifierEditor gridPane.setHgap(5); Label typeLabel = new Label("ESN"); + typeLabel.setLabelFor(getEsnField()); GridPane.setConstraints(typeLabel, 0, 0); gridPane.getChildren().add(typeLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/LojackEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/LojackEditor.java index 22f0c57f..e0df534d 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/LojackEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/LojackEditor.java @@ -47,6 +47,7 @@ public class LojackEditor extends IdentifierEditor gridPane.setHgap(5); Label typeLabel = new Label("LoJack Function"); + typeLabel.setLabelFor(getFunctionComboBox()); GridPane.setConstraints(typeLabel, 0, 0); gridPane.getChildren().add(typeLabel); @@ -57,6 +58,10 @@ public class LojackEditor extends IdentifierEditor gridPane.getChildren().add(getIdentifierField()); Label helpLabel = new Label("Format: 5 numbers, characters or (*)wildcard - e.g. AB*12"); + //The identifier field has no label of its own, so the help label provides its accessible name and help text + helpLabel.setLabelFor(getIdentifierField()); + helpLabel.setAccessibleText("LoJack ID"); + getIdentifierField().setAccessibleHelp(helpLabel.getText()); GridPane.setConstraints(helpLabel, 3, 0); gridPane.getChildren().add(helpLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/NXDNTypeDEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/NXDNTypeDEditor.java index fb5375af..e4a44245 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/NXDNTypeDEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/NXDNTypeDEditor.java @@ -76,11 +76,13 @@ public class NXDNTypeDEditor extends IdentifierE gridPane.add(new Label("NXDN Type-D"), 0, 0); Label homeRepeaterLabel = new Label("Home Repeater"); + homeRepeaterLabel.setLabelFor(mHomeRepeaterField); GridPane.setHalignment(homeRepeaterLabel, HPos.RIGHT); gridPane.add(homeRepeaterLabel, 1, 0); gridPane.add(mHomeRepeaterField, 2, 0); Label identifierFieldLabel = new Label(identifierLabel); + identifierFieldLabel.setLabelFor(mIdentifierField); GridPane.setHalignment(identifierFieldLabel, HPos.RIGHT); gridPane.add(identifierFieldLabel, 3, 0); gridPane.add(mIdentifierField, 4, 0); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/NXDNTypeDRangeEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/NXDNTypeDRangeEditor.java index f9a77256..c881bdeb 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/NXDNTypeDRangeEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/NXDNTypeDRangeEditor.java @@ -89,15 +89,21 @@ public class NXDNTypeDRangeEditor extends I gridPane.add(new Label("NXDN Type-D"), 0, 0); Label homeRepeaterLabel = new Label("Home Repeater"); + homeRepeaterLabel.setLabelFor(mHomeRepeaterField); GridPane.setHalignment(homeRepeaterLabel, HPos.RIGHT); gridPane.add(homeRepeaterLabel, 1, 0); gridPane.add(mHomeRepeaterField, 2, 0); Label rangeFieldLabel = new Label(rangeLabel); + rangeFieldLabel.setLabelFor(mMinField); + rangeFieldLabel.setAccessibleText(rangeLabel + " start"); GridPane.setHalignment(rangeFieldLabel, HPos.RIGHT); gridPane.add(rangeFieldLabel, 3, 0); gridPane.add(mMinField, 4, 0); - gridPane.add(new Label("-"), 5, 0); + Label dashLabel = new Label("-"); + dashLabel.setLabelFor(mMaxField); + dashLabel.setAccessibleText(rangeLabel + " end"); + gridPane.add(dashLabel, 5, 0); gridPane.add(mMaxField, 6, 0); getChildren().add(gridPane); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedRadioIdEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedRadioIdEditor.java index ae80f177..6557f7f2 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedRadioIdEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedRadioIdEditor.java @@ -70,6 +70,7 @@ public class P25FullyQualifiedRadioIdEditor extends IdentifierEditor gridPane.getChildren().add(getProtocolLabel()); Label valueLabel = new Label("Radio ID"); + valueLabel.setLabelFor(getRadioIdField()); GridPane.setHalignment(valueLabel, HPos.RIGHT); GridPane.setConstraints(valueLabel, 1, 0); gridPane.getChildren().add(valueLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/RadioIdRangeEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/RadioIdRangeEditor.java index 17c3388d..2a45174a 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/RadioIdRangeEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/RadioIdRangeEditor.java @@ -73,6 +73,8 @@ public class RadioIdRangeEditor extends IdentifierEditor gridPane.getChildren().add(getProtocolLabel()); Label valueLabel = new Label("Radio ID Range"); + valueLabel.setLabelFor(getMinRadioIdField()); + valueLabel.setAccessibleText("Radio ID Range start"); GridPane.setHalignment(valueLabel, HPos.RIGHT); GridPane.setConstraints(valueLabel, 1, 0); gridPane.getChildren().add(valueLabel); @@ -81,6 +83,8 @@ public class RadioIdRangeEditor extends IdentifierEditor gridPane.getChildren().add(getMinRadioIdField()); Label dashLabel = new Label("-"); + dashLabel.setLabelFor(getMaxRadioIdField()); + dashLabel.setAccessibleText("Radio ID Range end"); GridPane.setConstraints(dashLabel, 3, 0); gridPane.getChildren().add(dashLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TalkgroupEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TalkgroupEditor.java index 6825167e..80aa39e0 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TalkgroupEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TalkgroupEditor.java @@ -73,6 +73,7 @@ public class TalkgroupEditor extends IdentifierEditor gridPane.getChildren().add(getProtocolLabel()); Label valueLabel = new Label("Talkgroup"); + valueLabel.setLabelFor(getTalkgroupField()); GridPane.setHalignment(valueLabel, HPos.RIGHT); GridPane.setConstraints(valueLabel, 1, 0); gridPane.getChildren().add(valueLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TalkgroupRangeEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TalkgroupRangeEditor.java index 9e6e4ec1..024fb001 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TalkgroupRangeEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TalkgroupRangeEditor.java @@ -75,6 +75,8 @@ public class TalkgroupRangeEditor extends IdentifierEditor gridPane.getChildren().add(getProtocolLabel()); Label valueLabel = new Label("Talkgroup Range"); + valueLabel.setLabelFor(getMinTalkgroupField()); + valueLabel.setAccessibleText("Talkgroup Range start"); GridPane.setHalignment(valueLabel, HPos.RIGHT); GridPane.setConstraints(valueLabel, 1, 0); gridPane.getChildren().add(valueLabel); @@ -83,6 +85,8 @@ public class TalkgroupRangeEditor extends IdentifierEditor gridPane.getChildren().add(getMinTalkgroupField()); Label dashLabel = new Label("-"); + dashLabel.setLabelFor(getMaxTalkgroupField()); + dashLabel.setAccessibleText("Talkgroup Range end"); GridPane.setConstraints(dashLabel, 3, 0); gridPane.getChildren().add(dashLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TonesEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TonesEditor.java index 8ebb0107..60b1de9c 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TonesEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/TonesEditor.java @@ -73,6 +73,7 @@ public class TonesEditor extends IdentifierEditor editorsPane.setVgap(10); Label toneLabel = new Label("Tone"); + toneLabel.setLabelFor(getAmbeToneComboBox()); GridPane.setConstraints(toneLabel, 0, 0); GridPane.setHalignment(toneLabel, HPos.RIGHT); editorsPane.getChildren().add(toneLabel); @@ -81,6 +82,7 @@ public class TonesEditor extends IdentifierEditor editorsPane.getChildren().add(getAmbeToneComboBox()); Label durationLabel = new Label("Duration 1-50"); + durationLabel.setLabelFor(getDurationTextField()); GridPane.setConstraints(durationLabel, 0, 1); GridPane.setHalignment(durationLabel, HPos.RIGHT); editorsPane.getChildren().add(durationLabel); @@ -222,6 +224,7 @@ public class TonesEditor extends IdentifierEditor iconNode.setIconSize(12); iconNode.setFill(getMoveUpButton().getTextFill()); mMoveUpButton.setGraphic(iconNode); + mMoveUpButton.setAccessibleText("Move tone up"); mMoveUpButton.setMaxWidth(Double.MAX_VALUE); mMoveUpButton.setDisable(true); mMoveUpButton.setOnAction(event -> { @@ -249,6 +252,7 @@ public class TonesEditor extends IdentifierEditor iconNode.setIconSize(12); iconNode.setFill(getMoveDownButton().getTextFill()); mMoveDownButton.setGraphic(iconNode); + mMoveDownButton.setAccessibleText("Move tone down"); mMoveDownButton.setMaxWidth(Double.MAX_VALUE); mMoveDownButton.setDisable(true); mMoveDownButton.setOnAction(event -> { @@ -274,6 +278,7 @@ public class TonesEditor extends IdentifierEditor mToneListView = new ListView<>(); mToneListView.setDisable(true); mToneListView.setPrefHeight(75); + mToneListView.setAccessibleText("Tone sequence"); mToneListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { setTone(newValue); }); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/UnitStatusEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/UnitStatusEditor.java index f5526c3f..e109638e 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/UnitStatusEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/UnitStatusEditor.java @@ -58,6 +58,7 @@ public class UnitStatusEditor extends IdentifierEditor gridPane.setVgap(3); Label valueLabel = new Label("Unit Status"); + valueLabel.setLabelFor(getUnitStatusField()); GridPane.setHalignment(valueLabel, HPos.RIGHT); GridPane.setConstraints(valueLabel, 0, 0); gridPane.getChildren().add(valueLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/UserStatusEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/UserStatusEditor.java index 41ac75a1..a06a412a 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/UserStatusEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/UserStatusEditor.java @@ -51,6 +51,7 @@ public class UserStatusEditor extends IdentifierEditor gridPane.setVgap(3); Label valueLabel = new Label("User Status"); + valueLabel.setLabelFor(getUserStatusField()); GridPane.setHalignment(valueLabel, HPos.RIGHT); GridPane.setConstraints(valueLabel, 0, 0); gridPane.getChildren().add(valueLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/AMConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/AMConfigurationEditor.java index 3801cacc..a707142c 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/AMConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/AMConfigurationEditor.java @@ -19,6 +19,8 @@ package io.github.dsheirer.gui.playlist.channel; +import io.github.dsheirer.gui.control.AccessibilityUtil; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.DbPowerMeter; import io.github.dsheirer.gui.control.HexFormatter; import io.github.dsheirer.gui.control.IntegerFormatter; @@ -134,6 +136,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor int row = 0; Label bandwidthLabel = new Label("Channel Bandwidth"); + AccessibilityUtil.labelGroup(bandwidthLabel, getBandwidthButton()); GridPane.setHalignment(bandwidthLabel, HPos.RIGHT); GridPane.setConstraints(bandwidthLabel, 0, row); gridPane.getChildren().add(bandwidthLabel); @@ -142,6 +145,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getBandwidthButton()); Label squelchLabel = new Label("Squelch Threshold"); + squelchLabel.setLabelFor(getSquelchThresholdField()); GridPane.setHalignment(squelchLabel, HPos.RIGHT); GridPane.setConstraints(squelchLabel, 0, row); gridPane.getChildren().add(squelchLabel); @@ -150,6 +154,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getSquelchThresholdField()); Label autoTrackLabel = new Label("Squelch Auto-Track"); + autoTrackLabel.setLabelFor(getSquelchAutoTrackSwitch()); GridPane.setHalignment(autoTrackLabel, HPos.RIGHT); GridPane.setConstraints(autoTrackLabel, 2, row); gridPane.getChildren().add(autoTrackLabel); @@ -159,6 +164,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getSquelchAutoTrackSwitch()); Label talkgroupLabel = new Label("Talkgroup To Assign"); + talkgroupLabel.setLabelFor(getTalkgroupField()); GridPane.setHalignment(talkgroupLabel, HPos.RIGHT); GridPane.setConstraints(talkgroupLabel, 4, row); gridPane.getChildren().add(talkgroupLabel); @@ -210,6 +216,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getBasebandRecordSwitch()); Label recordBasebandLabel = new Label("Channel (Baseband I&Q)"); + recordBasebandLabel.setLabelFor(getBasebandRecordSwitch()); GridPane.setHalignment(recordBasebandLabel, HPos.LEFT); GridPane.setConstraints(recordBasebandLabel, 1, 1); gridPane.getChildren().add(recordBasebandLabel); @@ -254,7 +261,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor { if(mBasebandRecordSwitch == null) { - mBasebandRecordSwitch = new ToggleSwitch(); + mBasebandRecordSwitch = new AccessibleToggleSwitch(); mBasebandRecordSwitch.setDisable(true); mBasebandRecordSwitch.setTextAlignment(TextAlignment.RIGHT); mBasebandRecordSwitch.selectedProperty() @@ -348,7 +355,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor { if(mSquelchAutoTrackSwitch == null) { - mSquelchAutoTrackSwitch = new ToggleSwitch(); + mSquelchAutoTrackSwitch = new AccessibleToggleSwitch(); mSquelchAutoTrackSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java index f37ffa91..f3dc7a95 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java @@ -23,6 +23,7 @@ import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.ChannelException; import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.MaxLengthUnaryOperator; import io.github.dsheirer.gui.playlist.Editor; import io.github.dsheirer.gui.preference.PreferenceEditorType; @@ -464,7 +465,7 @@ public abstract class ChannelConfigurationEditor extends Editor { if(mAutoStartSwitch == null) { - mAutoStartSwitch = new ToggleSwitch(); + mAutoStartSwitch = new AccessibleToggleSwitch(); mAutoStartSwitch.setDisable(true); mAutoStartSwitch.setDisable(true); mAutoStartSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); @@ -533,6 +534,7 @@ public abstract class ChannelConfigurationEditor extends Editor int row = 0; Label systemLabel = new Label("System"); + systemLabel.setLabelFor(getSystemField()); GridPane.setHalignment(systemLabel, HPos.RIGHT); GridPane.setConstraints(systemLabel, 0, row); mTextFieldPane.getChildren().add(systemLabel); @@ -542,6 +544,7 @@ public abstract class ChannelConfigurationEditor extends Editor mTextFieldPane.getChildren().add(getSystemField()); Label autoStartLabel = new Label("Auto-Start"); + autoStartLabel.setLabelFor(getAutoStartSwitch()); GridPane.setHalignment(autoStartLabel, HPos.RIGHT); GridPane.setConstraints(autoStartLabel, 2, row); mTextFieldPane.getChildren().add(autoStartLabel); @@ -554,6 +557,7 @@ public abstract class ChannelConfigurationEditor extends Editor mTextFieldPane.getChildren().add(getPlayButton()); Label siteLabel = new Label("Site"); + siteLabel.setLabelFor(getSiteField()); GridPane.setHalignment(siteLabel, HPos.RIGHT); GridPane.setConstraints(siteLabel, 0, ++row); mTextFieldPane.getChildren().add(siteLabel); @@ -563,6 +567,7 @@ public abstract class ChannelConfigurationEditor extends Editor mTextFieldPane.getChildren().add(getSiteField()); Label autoStartOrderLabel = new Label("Start Order"); + autoStartOrderLabel.setLabelFor(getAutoStartOrderSpinner()); GridPane.setHalignment(autoStartOrderLabel, HPos.RIGHT); GridPane.setConstraints(autoStartOrderLabel, 2, row); mTextFieldPane.getChildren().add(autoStartOrderLabel); @@ -572,6 +577,7 @@ public abstract class ChannelConfigurationEditor extends Editor mTextFieldPane.getChildren().add(getAutoStartOrderSpinner()); Label nameLabel = new Label("Name"); + nameLabel.setLabelFor(getNameField()); GridPane.setHalignment(nameLabel, HPos.RIGHT); GridPane.setConstraints(nameLabel, 0, ++row); mTextFieldPane.getChildren().add(nameLabel); @@ -581,6 +587,7 @@ public abstract class ChannelConfigurationEditor extends Editor mTextFieldPane.getChildren().add(getNameField()); Label aliasListLabel = new Label("Alias List"); + aliasListLabel.setLabelFor(getAliasListComboBox()); GridPane.setHalignment(aliasListLabel, HPos.RIGHT); GridPane.setConstraints(aliasListLabel, 2, row); mTextFieldPane.getChildren().add(aliasListLabel); @@ -708,20 +715,25 @@ public abstract class ChannelConfigurationEditor extends Editor audioPane.add(new Label(title), 0, 0); Label noiseGainLabel = new Label("Comfort Noise Volume:"); + noiseGainLabel.setLabelFor(noiseGainSlider); GridPane.setHalignment(noiseGainLabel, HPos.RIGHT); audioPane.add(noiseGainLabel, 1, 0); - audioPane.add(createSliderWithReset(noiseGainSlider, DecodeConfigJMBE.DEFAULT_COMFORT_NOISE_GAIN), 2, 0); + audioPane.add(createSliderWithReset(noiseGainSlider, DecodeConfigJMBE.DEFAULT_COMFORT_NOISE_GAIN, + "Reset comfort noise volume"), 2, 0); Label agcLabel = new Label("AGC:"); + agcLabel.setLabelFor(agcSwitch); agcLabel.setTooltip(agcSwitch.getTooltip()); GridPane.setHalignment(agcLabel, HPos.RIGHT); audioPane.add(agcLabel, 3, 0); audioPane.add(agcSwitch, 4, 0); Label toneGainLabel = new Label("Tone Volume:"); + toneGainLabel.setLabelFor(toneGainSlider); GridPane.setHalignment(toneGainLabel, HPos.RIGHT); audioPane.add(toneGainLabel, 1, 1); - audioPane.add(createSliderWithReset(toneGainSlider, DecodeConfigJMBE.DEFAULT_TONE_GAIN), 2, 1); + audioPane.add(createSliderWithReset(toneGainSlider, DecodeConfigJMBE.DEFAULT_TONE_GAIN, "Reset tone volume"), 2, 1); Label legacyToneLevelsLabel = new Label("Use Legacy Tone Levels:"); + legacyToneLevelsLabel.setLabelFor(legacyToneLevelsSwitch); legacyToneLevelsLabel.setTooltip(legacyToneLevelsSwitch.getTooltip()); GridPane.setHalignment(legacyToneLevelsLabel, HPos.RIGHT); audioPane.add(legacyToneLevelsLabel, 3, 1); @@ -739,9 +751,10 @@ public abstract class ChannelConfigurationEditor extends Editor * current slider value and a small reset button. The label and button track the disabled state of the slider. * @param slider to wrap * @param defaultValue to apply to the slider when the reset button is clicked + * @param resetName accessible name for the reset button, announced by screen readers * @return box containing the slider, the value label and the reset button */ - private HBox createSliderWithReset(Slider slider, double defaultValue) + private HBox createSliderWithReset(Slider slider, double defaultValue, String resetName) { IconNode resetIcon = new IconNode(FontAwesome.UNDO); resetIcon.setIconSize(10); @@ -750,6 +763,7 @@ public abstract class ChannelConfigurationEditor extends Editor resetIcon.fillProperty().bind(resetButton.textFillProperty()); resetButton.setGraphic(resetIcon); resetButton.setTooltip(new Tooltip("Reset to default value")); + resetButton.setAccessibleText(resetName); resetButton.disableProperty().bind(slider.disableProperty()); resetButton.setOnAction(event -> slider.setValue(defaultValue)); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java index 961ce7dc..ad7b3e23 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java @@ -21,6 +21,7 @@ package io.github.dsheirer.gui.playlist.channel; import com.google.common.base.Joiner; import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.gui.control.AccessibilityUtil; import io.github.dsheirer.gui.playlist.IAliasListRefreshListener; import io.github.dsheirer.module.decode.DecoderFactory; import io.github.dsheirer.module.decode.DecoderType; @@ -336,6 +337,7 @@ public class ChannelEditor extends SplitPane implements IFilterProcessor, IAlias searchBox.setSpacing(5); searchBox.setAlignment(Pos.CENTER); Label searchLabel = new Label("Search:"); + searchLabel.setLabelFor(getSearchField()); searchLabel.setAlignment(Pos.CENTER_RIGHT); searchBox.getChildren().addAll(searchLabel, getSearchField()); @@ -343,6 +345,7 @@ public class ChannelEditor extends SplitPane implements IFilterProcessor, IAlias viewBox.setSpacing(5); viewBox.setAlignment(Pos.CENTER); Label viewLabel = new Label("View Channels:"); + AccessibilityUtil.labelGroup(viewLabel, getViewSegmentedButton()); viewBox.getChildren().addAll(viewLabel, getViewSegmentedButton()); mSearchAndViewBox.getChildren().addAll(searchBox, viewBox); @@ -409,6 +412,7 @@ public class ChannelEditor extends SplitPane implements IFilterProcessor, IAlias if(mChannelTableView == null) { mChannelTableView = new TableView<>(); + mChannelTableView.setAccessibleText("Channels"); TableColumn playingColumn = new TableColumn("Playing"); playingColumn.setPrefWidth(75); @@ -425,12 +429,14 @@ public class ChannelEditor extends SplitPane implements IFilterProcessor, IAlias if(empty || item == null || !item) { setGraphic(null); + setAccessibleText(empty ? null : "No"); } else { IconNode iconNode = new IconNode(FontAwesome.CHECK); iconNode.setFill(Color.GREEN); setGraphic(iconNode); + setAccessibleText("Yes"); } } }; @@ -453,12 +459,14 @@ public class ChannelEditor extends SplitPane implements IFilterProcessor, IAlias if(empty || item == null || !item) { setGraphic(null); + setAccessibleText(empty ? null : "No"); } else { IconNode iconNode = new IconNode(FontAwesome.CHECK); iconNode.setFill(Color.GREEN); setGraphic(iconNode); + setAccessibleText("Yes"); } } }; diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/DMRConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/DMRConfigurationEditor.java index 94ba3634..1a7f8415 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/DMRConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/DMRConfigurationEditor.java @@ -19,6 +19,7 @@ package io.github.dsheirer.gui.playlist.channel; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.IntegerTextField; import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor; import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor; @@ -142,6 +143,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor int row = 0; Label poolSizeLabel = new Label("Max Traffic Channels"); + poolSizeLabel.setLabelFor(getTrafficChannelPoolSizeSpinner()); GridPane.setHalignment(poolSizeLabel, HPos.RIGHT); GridPane.setConstraints(poolSizeLabel, 0, row); gridPane.getChildren().add(poolSizeLabel); @@ -153,6 +155,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getIgnoreDataCallsButton()); Label ignoreDataLabel = new Label("Ignore Data Calls"); + ignoreDataLabel.setLabelFor(getIgnoreDataCallsButton()); GridPane.setHalignment(ignoreDataLabel, HPos.LEFT); GridPane.setConstraints(ignoreDataLabel, 3, row); gridPane.getChildren().add(ignoreDataLabel); @@ -161,6 +164,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getIgnoreCRCChecksumsButton()); Label ignoreCRCLabel = new Label("Ignore CRC Checksums (RAS)"); + ignoreCRCLabel.setLabelFor(getIgnoreCRCChecksumsButton()); GridPane.setHalignment(ignoreCRCLabel, HPos.LEFT); GridPane.setConstraints(ignoreCRCLabel, 5, row); gridPane.getChildren().add(ignoreCRCLabel); @@ -169,6 +173,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getUseCompressedTalkgroupsToggle()); Label useCompressedTalkgroupsLabel = new Label("Use Compressed Talkgroups"); + useCompressedTalkgroupsLabel.setLabelFor(getUseCompressedTalkgroupsToggle()); GridPane.setHalignment(useCompressedTalkgroupsLabel, HPos.LEFT); GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row); gridPane.getChildren().add(useCompressedTalkgroupsLabel); @@ -196,9 +201,11 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor editorBox.setSpacing(5); Label lcnLabel = new Label("LCN"); + lcnLabel.setLabelFor(getLogicalChannelNumberField()); editorBox.getChildren().addAll(lcnLabel, getLogicalChannelNumberField()); Label downlinkLabel = new Label("Frequency (MHz)"); + downlinkLabel.setLabelFor(getDownlinkFrequencyField()); downlinkLabel.setPadding(new Insets(0,0,0,5)); editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField()); @@ -286,6 +293,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor if(mTimeslotFrequencyTable == null) { mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor())); + mTimeslotFrequencyTable.setAccessibleText("Logical channel number (LCN) to frequency map"); mTimeslotFrequencyTable.setPrefHeight(100.0); TableColumn numberColumn = new TableColumn("LCN"); @@ -346,6 +354,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor if(mAddTimeslotFrequencyButton == null) { mAddTimeslotFrequencyButton = new Button("Add"); + mAddTimeslotFrequencyButton.setAccessibleText("Add LCN frequency"); mAddTimeslotFrequencyButton.setMaxWidth(Double.MAX_VALUE); mAddTimeslotFrequencyButton.setOnAction(event -> addTimeslot()); } @@ -394,6 +403,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor if(mDeleteTimeslotFrequencyButton == null) { mDeleteTimeslotFrequencyButton = new Button("Delete"); + mDeleteTimeslotFrequencyButton.setAccessibleText("Delete LCN frequency"); mDeleteTimeslotFrequencyButton.setDisable(true); mDeleteTimeslotFrequencyButton.setMaxWidth(Double.MAX_VALUE); mDeleteTimeslotFrequencyButton.setOnAction(new EventHandler() @@ -501,7 +511,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor { if(mIgnoreDataCallsButton == null) { - mIgnoreDataCallsButton = new ToggleSwitch(); + mIgnoreDataCallsButton = new AccessibleToggleSwitch(); mIgnoreDataCallsButton.setDisable(true); mIgnoreDataCallsButton.selectedProperty() .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); @@ -514,7 +524,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor { if(mIgnoreCRCChecksumsButton == null) { - mIgnoreCRCChecksumsButton = new ToggleSwitch(); + mIgnoreCRCChecksumsButton = new AccessibleToggleSwitch(); mIgnoreCRCChecksumsButton.setDisable(true); mIgnoreCRCChecksumsButton.selectedProperty() .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); @@ -532,7 +542,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor { if(mUseCompressedTalkgroupsToggle == null) { - mUseCompressedTalkgroupsToggle = new ToggleSwitch(); + mUseCompressedTalkgroupsToggle = new AccessibleToggleSwitch(); mUseCompressedTalkgroupsToggle.setTooltip(new Tooltip("Use Compressed Talkgroups. This is only for Hytera Tier-III Trunked Systems")); mUseCompressedTalkgroupsToggle.setDisable(true); mUseCompressedTalkgroupsToggle.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); @@ -563,7 +573,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor { if(mJmbeAGCButton == null) { - mJmbeAGCButton = new ToggleSwitch(); + mJmbeAGCButton = new AccessibleToggleSwitch(); mJmbeAGCButton.setTooltip(new Tooltip("Automatic Gain Control to automatically adjust call audio volume")); mJmbeAGCButton.setDisable(true); mJmbeAGCButton.selectedProperty().addListener((o, old, n) -> modifiedProperty().set(true)); @@ -576,7 +586,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor { if(mLegacyToneLevelsButton == null) { - mLegacyToneLevelsButton = new ToggleSwitch(); + mLegacyToneLevelsButton = new AccessibleToggleSwitch(); mLegacyToneLevelsButton.setTooltip(new Tooltip( "Restore JMBE 1.x AMBE tone levels. Requires a supporting JMBE library; " + "stock libraries ignore this option. Tone Volume still adjusts the selected level.")); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRConfigurationEditor.java index e2da48a9..92589c51 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRConfigurationEditor.java @@ -19,6 +19,7 @@ package io.github.dsheirer.gui.playlist.channel; +import io.github.dsheirer.gui.control.AccessibilityUtil; import io.github.dsheirer.gui.playlist.decoder.AuxDecoderConfigurationEditor; import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor; import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor; @@ -146,6 +147,7 @@ public class LTRConfigurationEditor extends ChannelConfigurationEditor gridPane.setHgap(10); Label directionLabel = new Label("Direction"); + AccessibilityUtil.labelGroup(directionLabel, getDirectionButton()); GridPane.setHalignment(directionLabel, HPos.LEFT); GridPane.setConstraints(directionLabel, 0, 0); gridPane.getChildren().add(directionLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRNetConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRNetConfigurationEditor.java index e681d6e4..12095ddd 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRNetConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRNetConfigurationEditor.java @@ -19,6 +19,7 @@ package io.github.dsheirer.gui.playlist.channel; +import io.github.dsheirer.gui.control.AccessibilityUtil; import io.github.dsheirer.gui.playlist.decoder.AuxDecoderConfigurationEditor; import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor; import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor; @@ -146,6 +147,7 @@ public class LTRNetConfigurationEditor extends ChannelConfigurationEditor gridPane.setHgap(10); Label directionLabel = new Label("Direction"); + AccessibilityUtil.labelGroup(directionLabel, getDirectionButton()); GridPane.setHalignment(directionLabel, HPos.LEFT); GridPane.setConstraints(directionLabel, 0, 0); gridPane.getChildren().add(directionLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/MPT1327ConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/MPT1327ConfigurationEditor.java index 58885d72..97c25891 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/MPT1327ConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/MPT1327ConfigurationEditor.java @@ -126,6 +126,7 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor gridPane.setVgap(10); Label channelMapLabel = new Label("Channel Map"); + channelMapLabel.setLabelFor(getChannelMapComboBox()); GridPane.setHalignment(channelMapLabel, HPos.RIGHT); GridPane.setConstraints(channelMapLabel, 0, 0); gridPane.getChildren().add(channelMapLabel); @@ -138,6 +139,7 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getChannelMapEditButton()); Label poolSizeLabel = new Label("Max Traffic Channels"); + poolSizeLabel.setLabelFor(getTrafficChannelPoolSizeSpinner()); GridPane.setHalignment(poolSizeLabel, HPos.RIGHT); GridPane.setConstraints(poolSizeLabel, 0, 1); gridPane.getChildren().add(poolSizeLabel); @@ -146,6 +148,7 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getTrafficChannelPoolSizeSpinner()); Label callTimeoutLabel = new Label("Call Timeout Seconds"); + callTimeoutLabel.setLabelFor(getCallTimeoutSpinner()); GridPane.setHalignment(callTimeoutLabel, HPos.RIGHT); GridPane.setConstraints(callTimeoutLabel, 2, 1); gridPane.getChildren().add(callTimeoutLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/NBFMConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/NBFMConfigurationEditor.java index 4af3f85d..b31d3707 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/NBFMConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/NBFMConfigurationEditor.java @@ -19,6 +19,8 @@ package io.github.dsheirer.gui.playlist.channel; +import io.github.dsheirer.gui.control.AccessibilityUtil; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.HexFormatter; import io.github.dsheirer.gui.control.IntegerFormatter; import io.github.dsheirer.gui.playlist.decoder.AuxDecoderConfigurationEditor; @@ -135,6 +137,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor // channel bandwidth Label bandwidthLabel = new Label("Channel Bandwidth"); + AccessibilityUtil.labelGroup(bandwidthLabel, getBandwidthButton()); GridPane.setHalignment(bandwidthLabel, HPos.RIGHT); GridPane.setConstraints(bandwidthLabel, 0, 0); gridPane.getChildren().add(bandwidthLabel); @@ -144,6 +147,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor //De-emphasis Label deemphasisLabel = new Label("De-emphasis"); + deemphasisLabel.setLabelFor(getDeemphasisCombo()); GridPane.setHalignment(deemphasisLabel, HPos.RIGHT); GridPane.setConstraints(deemphasisLabel, 0, 1); gridPane.getChildren().add(deemphasisLabel); @@ -154,6 +158,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor // Note: normally the label and its toggle switch are one cell. They were split to make the next row // look better. The label is manually created and placed here. Might want to create a new grid pane instead. Label HPFLabel = new Label("High-pass Audio Filter (300 Hz)"); + HPFLabel.setLabelFor(getAudioFilterEnable()); GridPane.setHalignment(HPFLabel, HPos.RIGHT); GridPane.setConstraints(HPFLabel, 2, 1); gridPane.getChildren().add(HPFLabel); @@ -172,6 +177,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor */ // talkgroup Label talkgroupLabel = new Label("Talkgroup To Assign"); + talkgroupLabel.setLabelFor(getTalkgroupField()); GridPane.setHalignment(talkgroupLabel, HPos.RIGHT); GridPane.setConstraints(talkgroupLabel, 0, 2); gridPane.getChildren().add(talkgroupLabel); @@ -198,6 +204,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor modifiedProperty().set(true); }); GridPane.setConstraints(mSquelchTypeCombo, 3, 2); + typeLabel.setLabelFor(mSquelchTypeCombo); gridPane.getChildren().add(mSquelchTypeCombo); // CTCSS code selector @@ -318,6 +325,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getBasebandRecordSwitch()); Label recordBasebandLabel = new Label("Channel (Baseband I&Q)"); + recordBasebandLabel.setLabelFor(getBasebandRecordSwitch()); GridPane.setHalignment(recordBasebandLabel, HPos.LEFT); GridPane.setConstraints(recordBasebandLabel, 1, 0); gridPane.getChildren().add(recordBasebandLabel); @@ -381,7 +389,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor // removed label from toggle switch so that the label and the switch can be in different columns // to make things look better on the next line. Perhaps make a new grid pane instead. //mAudioFilterEnable = new ToggleSwitch("High-Pass Audio Filter (300 Hz)"); - mAudioFilterEnable = new ToggleSwitch(""); + mAudioFilterEnable = new AccessibleToggleSwitch(""); mAudioFilterEnable.setTooltip(new Tooltip("High-pass filter to remove DC offset and sub-audible signalling")); mAudioFilterEnable.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); } @@ -396,7 +404,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor { if(mALCEnable == null) { - mALCEnable = new ToggleSwitch("Automatic Level Control"); + mALCEnable = new AccessibleToggleSwitch("Automatic Level Control"); mALCEnable.setTooltip(new Tooltip("Automatic Level Control (ALC). Tries to make the volume of each received transmission the same.")); mALCEnable.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); } @@ -518,7 +526,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor { if(mBasebandRecordSwitch == null) { - mBasebandRecordSwitch = new ToggleSwitch(); + mBasebandRecordSwitch = new AccessibleToggleSwitch(); mBasebandRecordSwitch.setDisable(true); mBasebandRecordSwitch.setTextAlignment(TextAlignment.RIGHT); mBasebandRecordSwitch.selectedProperty() diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/NXDNConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/NXDNConfigurationEditor.java index e455f906..81366077 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/NXDNConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/NXDNConfigurationEditor.java @@ -19,6 +19,8 @@ package io.github.dsheirer.gui.playlist.channel; +import io.github.dsheirer.gui.control.AccessibilityUtil; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.IntegerTextField; import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor; import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor; @@ -148,6 +150,7 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor int row = 0; Label mode = new Label("Mode:"); + AccessibilityUtil.labelGroup(mode, getTransmissionModeButton()); GridPane.setHalignment(mode, HPos.RIGHT); GridPane.setConstraints(mode, 0, row); gridPane.getChildren().add(mode); @@ -156,6 +159,7 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getTransmissionModeButton()); Label encoding = new Label("Talker Alias Encoding:"); + encoding.setLabelFor(getEncodingComboBox()); GridPane.setHalignment(encoding, HPos.RIGHT); GridPane.setConstraints(encoding, 2, row); gridPane.getChildren().add(encoding); @@ -164,6 +168,7 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getEncodingComboBox()); Label poolSizeLabel = new Label("Max Traffic Channels"); + poolSizeLabel.setLabelFor(getTrafficChannelPoolSizeSpinner()); GridPane.setHalignment(poolSizeLabel, HPos.RIGHT); GridPane.setConstraints(poolSizeLabel, 4, row); gridPane.getChildren().add(poolSizeLabel); @@ -205,9 +210,11 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor editorBox.setSpacing(5); Label lcnLabel = new Label("Channel Number"); + lcnLabel.setLabelFor(getChannelField()); editorBox.getChildren().addAll(lcnLabel, getChannelField()); Label downlinkLabel = new Label("Downlink Frequency (MHz)"); + downlinkLabel.setLabelFor(getDownlinkFrequencyField()); downlinkLabel.setPadding(new Insets(0,0,0,5)); editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField()); @@ -312,6 +319,7 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor if(mChannelMapTable == null) { mChannelMapTable = new TableView<>(FXCollections.observableArrayList(ObservableChannelFrequency.extractor())); + mChannelMapTable.setAccessibleText("Channel number (LCN) to frequency map"); mChannelMapTable.setPrefHeight(100.0); TableColumn channelColumn = new TableColumn<>("Channel"); @@ -401,6 +409,7 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor if(mAddTimeslotFrequencyButton == null) { mAddTimeslotFrequencyButton = new Button("Add"); + mAddTimeslotFrequencyButton.setAccessibleText("Add channel frequency"); mAddTimeslotFrequencyButton.setMaxWidth(Double.MAX_VALUE); mAddTimeslotFrequencyButton.setOnAction(event -> addChannelFrequency()); } @@ -425,6 +434,7 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor if(mDeleteButton == null) { mDeleteButton = new Button("Delete"); + mDeleteButton.setAccessibleText("Delete channel frequency"); mDeleteButton.setDisable(true); mDeleteButton.setMaxWidth(Double.MAX_VALUE); mDeleteButton.setOnAction(event -> { @@ -519,7 +529,8 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor { if(mIgnoreDataCallsToggle == null) { - mIgnoreDataCallsToggle = new ToggleSwitch("Data Calls"); + mIgnoreDataCallsToggle = new AccessibleToggleSwitch("Data Calls"); + mIgnoreDataCallsToggle.setAccessibleText("Ignore data calls"); mIgnoreDataCallsToggle.setDisable(true); mIgnoreDataCallsToggle.selectedProperty().addListener((o, ov, nv) -> modifiedProperty().set(true)); } @@ -534,7 +545,8 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor { if(mIgnoreEncryptedCallsToggle == null) { - mIgnoreEncryptedCallsToggle = new ToggleSwitch("Encrypted Calls"); + mIgnoreEncryptedCallsToggle = new AccessibleToggleSwitch("Encrypted Calls"); + mIgnoreEncryptedCallsToggle.setAccessibleText("Ignore encrypted calls"); mIgnoreEncryptedCallsToggle.setDisable(true); mIgnoreEncryptedCallsToggle.selectedProperty().addListener((o, ov, nv) -> modifiedProperty().set(true)); } @@ -546,7 +558,7 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor { if(mJmbeAGCButton == null) { - mJmbeAGCButton = new ToggleSwitch(); + mJmbeAGCButton = new AccessibleToggleSwitch(); mJmbeAGCButton.setTooltip(new Tooltip("Automatic Gain Control to automatically adjust call audio volume")); mJmbeAGCButton.setDisable(true); mJmbeAGCButton.selectedProperty().addListener((o, old, n) -> modifiedProperty().set(true)); @@ -559,7 +571,7 @@ public class NXDNConfigurationEditor extends ChannelConfigurationEditor { if(mJmbeLegacyToneLevelsButton == null) { - mJmbeLegacyToneLevelsButton = new ToggleSwitch(); + mJmbeLegacyToneLevelsButton = new AccessibleToggleSwitch(); mJmbeLegacyToneLevelsButton.setTooltip(new Tooltip( "Restore JMBE 1.x AMBE tone levels. Requires a supporting JMBE library; " + "stock libraries ignore this option. Tone Volume still adjusts the selected level.")); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java index c82ac9be..af6daf4e 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java @@ -19,6 +19,8 @@ package io.github.dsheirer.gui.playlist.channel; +import io.github.dsheirer.gui.control.AccessibilityUtil; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor; import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor; import io.github.dsheirer.gui.playlist.source.FrequencyEditor; @@ -129,6 +131,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor int row = 0; Label modulationLabel = new Label("Modulation"); + AccessibilityUtil.labelGroup(modulationLabel, getModulationSegmentedButton()); GridPane.setHalignment(modulationLabel, HPos.RIGHT); GridPane.setConstraints(modulationLabel, 0, row); gridPane.getChildren().add(modulationLabel); @@ -137,6 +140,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().addAll(getModulationSegmentedButton()); Label poolSizeLabel = new Label("Max Traffic Channels"); + poolSizeLabel.setLabelFor(getTrafficChannelPoolSizeSpinner()); GridPane.setHalignment(poolSizeLabel, HPos.RIGHT); GridPane.setConstraints(poolSizeLabel, 2, row); gridPane.getChildren().add(poolSizeLabel); @@ -148,6 +152,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getIgnoreDataCallsButton()); Label directionLabel = new Label("Ignore Data Calls"); + directionLabel.setLabelFor(getIgnoreDataCallsButton()); GridPane.setHalignment(directionLabel, HPos.LEFT); GridPane.setConstraints(directionLabel, 5, row); gridPane.getChildren().add(directionLabel); @@ -268,7 +273,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor { if(mJmbeAGCButton == null) { - mJmbeAGCButton = new ToggleSwitch(); + mJmbeAGCButton = new AccessibleToggleSwitch(); mJmbeAGCButton.setTooltip(new Tooltip("Automatic Gain Control to automatically adjust call audio volume")); mJmbeAGCButton.setDisable(true); mJmbeAGCButton.selectedProperty().addListener((o, old, n) -> modifiedProperty().set(true)); @@ -281,7 +286,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor { if(mLegacyToneLevelsButton == null) { - mLegacyToneLevelsButton = new ToggleSwitch(); + mLegacyToneLevelsButton = new AccessibleToggleSwitch(); mLegacyToneLevelsButton.setTooltip(new Tooltip( "Restore JMBE 1.x AMBE tone levels. Requires a supporting JMBE library; " + "stock libraries ignore this option. Tone Volume still adjusts the selected level.")); @@ -342,7 +347,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor { if(mIgnoreDataCallsButton == null) { - mIgnoreDataCallsButton = new ToggleSwitch(); + mIgnoreDataCallsButton = new AccessibleToggleSwitch(); mIgnoreDataCallsButton.setDisable(true); mIgnoreDataCallsButton.selectedProperty() .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java index f0161a82..916e1302 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java @@ -19,6 +19,7 @@ package io.github.dsheirer.gui.playlist.channel; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.IntegerTextField; import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor; import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor; @@ -124,6 +125,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor int row = 0; Label poolSizeLabel = new Label("Max Traffic Channels"); + poolSizeLabel.setLabelFor(getTrafficChannelPoolSizeSpinner()); GridPane.setHalignment(poolSizeLabel, HPos.RIGHT); GridPane.setConstraints(poolSizeLabel, 0, row); gridPane.getChildren().add(poolSizeLabel); @@ -135,11 +137,13 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getIgnoreDataCallsButton()); Label directionLabel = new Label("Ignore Data Calls"); + directionLabel.setLabelFor(getIgnoreDataCallsButton()); GridPane.setHalignment(directionLabel, HPos.LEFT); GridPane.setConstraints(directionLabel, 3, row); gridPane.getChildren().add(directionLabel); Label wacnLabel = new Label("WACN"); + wacnLabel.setLabelFor(getWacnTextField()); GridPane.setHalignment(wacnLabel, HPos.RIGHT); GridPane.setConstraints(wacnLabel, 0, ++row); gridPane.getChildren().add(wacnLabel); @@ -148,6 +152,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getWacnTextField()); Label systemLabel = new Label("System"); + systemLabel.setLabelFor(getSystemTextField()); GridPane.setHalignment(systemLabel, HPos.RIGHT); GridPane.setConstraints(systemLabel, 2, row); gridPane.getChildren().add(systemLabel); @@ -156,6 +161,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor gridPane.getChildren().add(getSystemTextField()); Label nacLabel = new Label("NAC"); + nacLabel.setLabelFor(getNacTextField()); GridPane.setHalignment(nacLabel, HPos.RIGHT); GridPane.setConstraints(nacLabel, 4, row); gridPane.getChildren().add(nacLabel); @@ -183,7 +189,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor { if(mJmbeAGCButton == null) { - mJmbeAGCButton = new ToggleSwitch(); + mJmbeAGCButton = new AccessibleToggleSwitch(); mJmbeAGCButton.setTooltip(new Tooltip("Automatic Gain Control to automatically adjust call audio volume")); mJmbeAGCButton.setDisable(true); mJmbeAGCButton.selectedProperty().addListener((o, old, n) -> modifiedProperty().set(true)); @@ -196,7 +202,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor { if(mLegacyToneLevelsButton == null) { - mLegacyToneLevelsButton = new ToggleSwitch(); + mLegacyToneLevelsButton = new AccessibleToggleSwitch(); mLegacyToneLevelsButton.setTooltip(new Tooltip( "Restore JMBE 1.x AMBE tone levels. Requires a supporting JMBE library; " + "stock libraries ignore this option. Tone Volume still adjusts the selected level.")); @@ -298,7 +304,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor { if(mIgnoreDataCallsButton == null) { - mIgnoreDataCallsButton = new ToggleSwitch(); + mIgnoreDataCallsButton = new AccessibleToggleSwitch(); mIgnoreDataCallsButton.setDisable(true); mIgnoreDataCallsButton.selectedProperty() .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channelMap/ChannelMapEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channelMap/ChannelMapEditor.java index 06fa576e..ca5c2a9e 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channelMap/ChannelMapEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channelMap/ChannelMapEditor.java @@ -276,6 +276,7 @@ public class ChannelMapEditor extends SplitPane if(mChannelMapListView == null) { mChannelMapListView = new ListView<>(); + mChannelMapListView.setAccessibleText("Channel maps"); mChannelMapListView.setItems(mChannelMapModel.getChannelMaps()); mChannelMapListView.getSelectionModel().selectedItemProperty() .addListener((observable, oldValue, newValue) -> { @@ -390,6 +391,7 @@ public class ChannelMapEditor extends SplitPane mChannelRangePane.setHgap(5); Label firstLabel = new Label("First Channel"); + firstLabel.setLabelFor(getFirstField()); GridPane.setHalignment(firstLabel, HPos.RIGHT); GridPane.setConstraints(firstLabel, 0, 0); mChannelRangePane.getChildren().add(firstLabel); @@ -399,6 +401,7 @@ public class ChannelMapEditor extends SplitPane mChannelRangePane.getChildren().add(getFirstField()); Label lastLabel = new Label("Last Channel"); + lastLabel.setLabelFor(getLastField()); GridPane.setHalignment(lastLabel, HPos.RIGHT); GridPane.setConstraints(lastLabel, 0, 1); mChannelRangePane.getChildren().add(lastLabel); @@ -408,6 +411,7 @@ public class ChannelMapEditor extends SplitPane mChannelRangePane.getChildren().add(getLastField()); Label baseLabel = new Label("Base Frequency (Hz)"); + baseLabel.setLabelFor(getBaseFrequencyField()); GridPane.setHalignment(baseLabel, HPos.RIGHT); GridPane.setConstraints(baseLabel, 0, 2); mChannelRangePane.getChildren().add(baseLabel); @@ -417,6 +421,7 @@ public class ChannelMapEditor extends SplitPane mChannelRangePane.getChildren().add(getBaseFrequencyField()); Label stepSizeLabel = new Label("Step Size (Hz)"); + stepSizeLabel.setLabelFor(getStepSizeField()); GridPane.setHalignment(stepSizeLabel, HPos.RIGHT); GridPane.setConstraints(stepSizeLabel, 0, 3); mChannelRangePane.getChildren().add(stepSizeLabel); @@ -445,7 +450,9 @@ public class ChannelMapEditor extends SplitPane nameBox.setSpacing(10); nameBox.setAlignment(Pos.CENTER); HBox.setHgrow(getNameField(), Priority.ALWAYS); - nameBox.getChildren().addAll(new Label("Name"), getNameField()); + Label nameLabel = new Label("Name"); + nameLabel.setLabelFor(getNameField()); + nameBox.getChildren().addAll(nameLabel, getNameField()); VBox contentsBox = new VBox(); VBox.setVgrow(nameBox, Priority.NEVER); @@ -599,6 +606,7 @@ public class ChannelMapEditor extends SplitPane if(mChannelRangeTableView == null) { mChannelRangeTableView = new TableView<>(); + mChannelRangeTableView.setAccessibleText("Channel ranges"); TableColumn firstColumn = new TableColumn("First"); firstColumn.setCellValueFactory(new PropertyValueFactory("firstChannel")); @@ -790,6 +798,8 @@ public class ChannelMapEditor extends SplitPane item == null || empty); this.setStyle(invalidRange ? BACKGROUND_COLOR_RED : BACKGROND_COLOR_NONE); + //Invalid ranges are only shown in red, so also report them to screen readers + setAccessibleText(invalidRange && item != null && !empty ? item + ", invalid or overlapping range" : null); } @Override diff --git a/src/main/java/io/github/dsheirer/gui/playlist/decoder/AuxDecoderConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/decoder/AuxDecoderConfigurationEditor.java index 499a470a..207fdc55 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/decoder/AuxDecoderConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/decoder/AuxDecoderConfigurationEditor.java @@ -22,6 +22,7 @@ package io.github.dsheirer.gui.playlist.decoder; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.playlist.Editor; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration; @@ -121,6 +122,7 @@ public class AuxDecoderConfigurationEditor extends Editor modifiedProperty().set(true)); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/eventlog/EventLogConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/eventlog/EventLogConfigurationEditor.java index 55ad9870..43afea59 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/eventlog/EventLogConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/eventlog/EventLogConfigurationEditor.java @@ -22,6 +22,7 @@ package io.github.dsheirer.gui.playlist.eventlog; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.playlist.Editor; import io.github.dsheirer.module.log.EventLogType; import io.github.dsheirer.module.log.config.EventLogConfiguration; @@ -117,6 +118,7 @@ public class EventLogConfigurationEditor extends Editor getChildren().add(getToggleSwitch()); Label label = new Label(mEventLogType.getDisplayString()); + label.setLabelFor(getToggleSwitch()); GridPane.setHalignment(label, HPos.LEFT); GridPane.setConstraints(label, 1, 0); getChildren().add(label); @@ -126,7 +128,7 @@ public class EventLogConfigurationEditor extends Editor { if(mToggleSwitch == null) { - mToggleSwitch = new ToggleSwitch(); + mToggleSwitch = new AccessibleToggleSwitch(); mToggleSwitch.setDisable(true); mToggleSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/manager/PlaylistManagerEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/manager/PlaylistManagerEditor.java index 1c8c6e9c..fb987cf3 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/manager/PlaylistManagerEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/manager/PlaylistManagerEditor.java @@ -127,6 +127,7 @@ public class PlaylistManagerEditor extends HBox if(mPlaylistTableView == null) { mPlaylistTableView = new TableView<>(); + mPlaylistTableView.setAccessibleText("Playlists"); TableColumn iconColumn = new TableColumn("Selected"); iconColumn.setPrefWidth(100); iconColumn.setCellFactory(new Callback,TableCell>() @@ -144,6 +145,7 @@ public class PlaylistManagerEditor extends HBox if(empty || getTableRow() == null || getTableRow().getItem() == null) { setGraphic(null); + setAccessibleText(null); } else { @@ -154,16 +156,19 @@ public class PlaylistManagerEditor extends HBox IconNode iconNode = new IconNode(FontAwesome.CHECK); iconNode.setFill(Color.GREEN); setGraphic(iconNode); + setAccessibleText("Selected"); } else if(!path.toFile().exists()) { IconNode iconNode = new IconNode(FontAwesome.TIMES); iconNode.setFill(Color.RED); setGraphic(iconNode); + setAccessibleText("File not found"); } else { setGraphic(null); + setAccessibleText("Not selected"); } } } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java index ee86b213..5b2884fe 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java @@ -138,6 +138,7 @@ public class AgencyEditor extends VBox if(mAgencyListView == null) { mAgencyListView = new ListView<>(); + mAgencyListView.setAccessibleText("Agencies"); mAgencyListView.setPrefHeight(200); mAgencyListView.setCellFactory(param -> new AgencyListCell()); mAgencyListView.getSelectionModel().selectedItemProperty() diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java index 03d47ea3..5fa8acac 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java @@ -94,6 +94,7 @@ public class AgencyFrequencyEditor extends GridPane getColumnConstraints().addAll(column1, column2, column3); Label categoryLabel = new Label("Category"); + categoryLabel.setLabelFor(getCategoryComboBox()); GridPane.setConstraints(categoryLabel, 0, row); GridPane.setHalignment(categoryLabel, HPos.RIGHT); getChildren().add(categoryLabel); @@ -103,6 +104,7 @@ public class AgencyFrequencyEditor extends GridPane getChildren().add(getCategoryComboBox()); Label subCategoryLabel = new Label("Sub-Category"); + subCategoryLabel.setLabelFor(getSubCategoryComboBox()); GridPane.setConstraints(subCategoryLabel, 0, ++row); GridPane.setHalignment(subCategoryLabel, HPos.RIGHT); getChildren().add(subCategoryLabel); @@ -207,6 +209,7 @@ public class AgencyFrequencyEditor extends GridPane if(mFrequencyTableView == null) { mFrequencyTableView = new TableView<>(); + mFrequencyTableView.setAccessibleText("Agency frequencies"); mFrequencyTableView.setMaxHeight(Double.MAX_VALUE); mFrequencyTableView.setPlaceholder(getPlaceholderLabel()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java index 8b028c7c..86e3b8e0 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java @@ -97,6 +97,7 @@ public class FrequencyEditor extends VBox gridPane.getChildren().add(detailsLabel); Label alphaLabel = new Label("Alpha Tag"); + alphaLabel.setLabelFor(getAlphaTagTextField()); GridPane.setHalignment(alphaLabel, HPos.RIGHT); GridPane.setConstraints(alphaLabel, 0, ++row); gridPane.getChildren().add(alphaLabel); @@ -106,6 +107,7 @@ public class FrequencyEditor extends VBox gridPane.getChildren().add(getAlphaTagTextField()); Label toneLabel = new Label("Tone"); + toneLabel.setLabelFor(getToneTextField()); GridPane.setHalignment(toneLabel, HPos.RIGHT); GridPane.setConstraints(toneLabel, 0, ++row); gridPane.getChildren().add(toneLabel); @@ -115,6 +117,7 @@ public class FrequencyEditor extends VBox gridPane.getChildren().add(getToneTextField()); Label modeLabel = new Label("Mode"); + modeLabel.setLabelFor(getModeTextField()); GridPane.setHalignment(modeLabel, HPos.RIGHT); GridPane.setConstraints(modeLabel, 0, ++row); gridPane.getChildren().add(modeLabel); @@ -124,6 +127,7 @@ public class FrequencyEditor extends VBox gridPane.getChildren().add(getModeTextField()); Label frequencyLabel = new Label("Frequency"); + frequencyLabel.setLabelFor(getFrequencyTextField()); GridPane.setHalignment(frequencyLabel, HPos.RIGHT); GridPane.setConstraints(frequencyLabel, 0, ++row); gridPane.getChildren().add(frequencyLabel); @@ -141,6 +145,7 @@ public class FrequencyEditor extends VBox gridPane.getChildren().add(createLabel); Label systemLabel = new Label("System"); + systemLabel.setLabelFor(getSystemTextField()); GridPane.setHalignment(systemLabel, HPos.RIGHT); GridPane.setConstraints(systemLabel, 0, ++row); gridPane.getChildren().add(systemLabel); @@ -150,6 +155,7 @@ public class FrequencyEditor extends VBox gridPane.getChildren().add(getSystemTextField()); Label siteLabel = new Label("Site"); + siteLabel.setLabelFor(getSiteTextField()); GridPane.setHalignment(siteLabel, HPos.RIGHT); GridPane.setConstraints(siteLabel, 0, ++row); gridPane.getChildren().add(siteLabel); @@ -159,6 +165,7 @@ public class FrequencyEditor extends VBox gridPane.getChildren().add(getSiteTextField()); Label nameLabel = new Label("Name"); + nameLabel.setLabelFor(getNameTextField()); GridPane.setHalignment(nameLabel, HPos.RIGHT); GridPane.setConstraints(nameLabel, 0, ++row); gridPane.getChildren().add(nameLabel); @@ -168,6 +175,7 @@ public class FrequencyEditor extends VBox gridPane.getChildren().add(getNameTextField()); Label decoderLabel = new Label("Decoder"); + decoderLabel.setLabelFor(getDecoderTextField()); GridPane.setHalignment(decoderLabel, HPos.RIGHT); GridPane.setConstraints(decoderLabel, 0, ++row); gridPane.getChildren().add(decoderLabel); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/LoginDialog.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/LoginDialog.java index f30ef455..4ab81c1c 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/LoginDialog.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/LoginDialog.java @@ -25,6 +25,7 @@ import io.github.dsheirer.rrapi.response.Fault; import io.github.dsheirer.rrapi.type.AuthorizationInformation; import io.github.dsheirer.service.radioreference.RadioReference; import javafx.application.Platform; +import javafx.beans.binding.Bindings; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.HPos; @@ -128,6 +129,7 @@ public class LoginDialog extends Dialog mGridPane.setVgap(5.0); Label userNameLabel = new Label("User Name:"); + userNameLabel.setLabelFor(getUserNameText()); GridPane.setHalignment(userNameLabel, HPos.RIGHT); GridPane.setConstraints(userNameLabel, 0, 0); mGridPane.getChildren().add(userNameLabel); @@ -137,6 +139,8 @@ public class LoginDialog extends Dialog mGridPane.getChildren().add(getUserNameText()); Label passwordLabel = new Label("Password:"); + passwordLabel.labelForProperty().bind(Bindings.when(getPasswordText().visibleProperty()) + .then((Node)getPasswordText()).otherwise(getPasswordField())); GridPane.setHalignment(passwordLabel, HPos.RIGHT); GridPane.setConstraints(passwordLabel, 0, 1); mGridPane.getChildren().add(passwordLabel); @@ -229,6 +233,7 @@ public class LoginDialog extends Dialog if(mShowPasswordCheckBox == null) { mShowPasswordCheckBox = new CheckBox("Show"); + mShowPasswordCheckBox.setAccessibleText("Show password"); mShowPasswordCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().getShowPassword()); mShowPasswordCheckBox.setOnAction(new EventHandler() { @@ -281,6 +286,7 @@ public class LoginDialog extends Dialog public void handle(ActionEvent event) { getTestConnectionButton().setDisable(true); + getTestConnectionButton().setAccessibleText(null); getTestPassIcon().setVisible(false); getTestFailIcon().setVisible(false); getTestExpiredIcon().setVisible(false); @@ -327,6 +333,8 @@ public class LoginDialog extends Dialog getTestPassIcon().setVisible(true); getTestFailIcon().setVisible(false); getTestExpiredIcon().setVisible(false); + //Success is only shown as an icon, so report it in the button's accessible name + getTestConnectionButton().setAccessibleText("Test Connection: login successful"); } else if (loginStatus == RadioReference.LoginStatus.EXPIRED_PREMIUM) { diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java index 9a8fdd9a..7314125d 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java @@ -377,6 +377,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer(); + mSiteFrequencyTableView.setAccessibleText("Site frequencies"); mSiteFrequencyTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); mSiteFrequencyTableView.setPlaceholder(new Label("Please select a site to view frequencies")); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java index 89409ba2..7019493b 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java @@ -103,7 +103,9 @@ public class SystemEditor extends VBox systemBox.setAlignment(Pos.CENTER_LEFT); systemBox.setSpacing(5); systemBox.setMaxWidth(Double.MAX_VALUE); - systemBox.getChildren().addAll(new Label("System"), getSystemComboBox()); + Label systemLabel = new Label("System"); + systemLabel.setLabelFor(getSystemComboBox()); + systemBox.getChildren().addAll(systemLabel, getSystemComboBox()); VBox.setVgrow(getTabPane(), Priority.ALWAYS); getChildren().addAll(systemBox, getTabPane()); } @@ -388,11 +390,14 @@ public class SystemEditor extends VBox setGraphic(null); mName.setText(null); mProtocol.setText(null); + setAccessibleText(null); } else { mName.setText(item.getName()); mProtocol.setText(getType(item)); + //Text is shown in nested labels, so expose it as the cell's accessible text + setAccessibleText(item.getName() + ", " + getType(item)); if(isSupported(item)) { diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemSiteSelectionEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemSiteSelectionEditor.java index 8be96c12..a04f2d20 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemSiteSelectionEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemSiteSelectionEditor.java @@ -214,6 +214,7 @@ public class SystemSiteSelectionEditor extends GridPane if(mSiteTableView == null) { mSiteTableView = new TableView<>(); + mSiteTableView.setAccessibleText("Sites"); mSiteTableView.setPlaceholder(getPlaceholderLabel()); TableColumn systemColumn = new TableColumn(); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemTalkgroupSelectionEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemTalkgroupSelectionEditor.java index e88637b0..2529db33 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemTalkgroupSelectionEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemTalkgroupSelectionEditor.java @@ -128,6 +128,7 @@ public class SystemTalkgroupSelectionEditor extends GridPane listBox.setSpacing(5); listBox.setAlignment(Pos.CENTER); Label importLabel = new Label("Import To Alias List:"); + importLabel.setLabelFor(getAliasListNameComboBox()); listBox.getChildren().addAll(importLabel, getAliasListNameComboBox(), getNewAliasListButton()); GridPane.setConstraints(listBox, 0, row); getChildren().add(listBox); @@ -139,7 +140,9 @@ public class SystemTalkgroupSelectionEditor extends GridPane HBox searchBox = new HBox(); searchBox.setSpacing(5); searchBox.setAlignment(Pos.CENTER); - searchBox.getChildren().addAll(new Label("Search"), getSearchField()); + Label searchLabel = new Label("Search"); + searchLabel.setLabelFor(getSearchField()); + searchBox.getChildren().addAll(searchLabel, getSearchField()); GridPane.setConstraints(searchBox, 0, ++row); getChildren().add(searchBox); @@ -151,7 +154,9 @@ public class SystemTalkgroupSelectionEditor extends GridPane categoryBox.setAlignment(Pos.CENTER_LEFT); categoryBox.setSpacing(5); HBox.setHgrow(getTalkgroupCategoryComboBox(), Priority.ALWAYS); - categoryBox.getChildren().addAll(new Label("Category"), getTalkgroupCategoryComboBox()); + Label categoryLabel = new Label("Category"); + categoryLabel.setLabelFor(getTalkgroupCategoryComboBox()); + categoryBox.getChildren().addAll(categoryLabel, getTalkgroupCategoryComboBox()); GridPane.setHgrow(categoryBox, Priority.ALWAYS); GridPane.setConstraints(categoryBox, 0, ++row); getChildren().add(categoryBox); @@ -612,6 +617,7 @@ public class SystemTalkgroupSelectionEditor extends GridPane if(mTalkgroupTableView == null) { mTalkgroupTableView = new TableView<>(); + mTalkgroupTableView.setAccessibleText("Talkgroups"); mTalkgroupTableView.setMaxHeight(Double.MAX_VALUE); TableColumn talkgroupColumn = new TableColumn("Talkgroup"); talkgroupColumn.setCellValueFactory(new PropertyValueFactory<>("talkgroup")); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/TalkgroupEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/TalkgroupEditor.java index 5f7ec8ef..849521e7 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/TalkgroupEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/TalkgroupEditor.java @@ -80,6 +80,7 @@ public class TalkgroupEditor extends GridPane getChildren().add(radioReferenceLabel); Label talkgroupLabel = new Label("Talkgroup"); + talkgroupLabel.setLabelFor(getTalkgroupTextField()); GridPane.setConstraints(talkgroupLabel, 0, ++row); GridPane.setHalignment(talkgroupLabel, HPos.RIGHT); getChildren().add(talkgroupLabel); @@ -89,6 +90,7 @@ public class TalkgroupEditor extends GridPane getChildren().add(getTalkgroupTextField()); Label alphaLabel = new Label("Alpha Tag"); + alphaLabel.setLabelFor(getAlphaTagTextField()); GridPane.setConstraints(alphaLabel, 0, ++row); GridPane.setHalignment(alphaLabel, HPos.RIGHT); getChildren().add(alphaLabel); @@ -98,6 +100,7 @@ public class TalkgroupEditor extends GridPane getChildren().add(getAlphaTagTextField()); Label descriptionLabel = new Label("Description"); + descriptionLabel.setLabelFor(getDescriptionTextField()); GridPane.setConstraints(descriptionLabel, 0, ++row); GridPane.setHalignment(descriptionLabel, HPos.RIGHT); getChildren().add(descriptionLabel); @@ -107,6 +110,7 @@ public class TalkgroupEditor extends GridPane getChildren().add(getDescriptionTextField()); Label modeLabel = new Label("Mode"); + modeLabel.setLabelFor(getModeTextField()); GridPane.setConstraints(modeLabel, 0, ++row); GridPane.setHalignment(modeLabel, HPos.RIGHT); getChildren().add(modeLabel); @@ -222,6 +226,7 @@ public class TalkgroupEditor extends GridPane if(mNameLabel == null) { mNameLabel = new Label("Name"); + mNameLabel.setLabelFor(getAliasNameTextField()); mNameLabel.setVisible(false); } @@ -233,6 +238,7 @@ public class TalkgroupEditor extends GridPane if(mGroupLabel == null) { mGroupLabel = new Label("Group"); + mGroupLabel.setLabelFor(getAliasGroupTextField()); mGroupLabel.setVisible(false); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/record/RecordConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/record/RecordConfigurationEditor.java index 305b2f54..efd63189 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/record/RecordConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/record/RecordConfigurationEditor.java @@ -22,6 +22,7 @@ package io.github.dsheirer.gui.playlist.record; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.playlist.Editor; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.config.RecordConfiguration; @@ -117,6 +118,7 @@ public class RecordConfigurationEditor extends Editor getChildren().add(getToggleSwitch()); Label label = new Label(mRecorderType.getDisplayString()); + label.setLabelFor(getToggleSwitch()); GridPane.setHalignment(label, HPos.LEFT); GridPane.setConstraints(label, 1, 0); getChildren().add(label); @@ -126,7 +128,7 @@ public class RecordConfigurationEditor extends Editor { if(mToggleSwitch == null) { - mToggleSwitch = new ToggleSwitch(); + mToggleSwitch = new AccessibleToggleSwitch(); mToggleSwitch.setDisable(true); mToggleSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/source/FrequencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/source/FrequencyEditor.java index e3753102..cca40e14 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/source/FrequencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/source/FrequencyEditor.java @@ -104,10 +104,12 @@ public class FrequencyEditor extends SourceConfigurationEditor addFrequencyBox()); } @@ -553,6 +557,7 @@ public class FrequencyEditor extends SourceConfigurationEditor { Alert confirm = new Alert(Alert.AlertType.CONFIRMATION, diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractBroadcastEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractBroadcastEditor.java index a0d7c566..cf58a327 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractBroadcastEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractBroadcastEditor.java @@ -22,6 +22,7 @@ package io.github.dsheirer.gui.playlist.streaming; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.audio.broadcast.BroadcastEvent; import io.github.dsheirer.audio.broadcast.BroadcastServerType; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.playlist.Editor; import io.github.dsheirer.playlist.PlaylistManager; import javafx.application.Platform; @@ -205,7 +206,7 @@ public abstract class AbstractBroadcastEditor { if(mEnabledSwitch == null) { - mEnabledSwitch = new ToggleSwitch(); + mEnabledSwitch = new AccessibleToggleSwitch(); mEnabledSwitch.setDisable(true); mEnabledSwitch.selectedProperty() .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractStreamEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractStreamEditor.java index 6c0f88b4..906feabf 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractStreamEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/AbstractStreamEditor.java @@ -25,7 +25,10 @@ package io.github.dsheirer.gui.playlist.streaming; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.gui.control.IntegerTextField; import io.github.dsheirer.playlist.PlaylistManager; +import javafx.beans.binding.Bindings; +import javafx.scene.Node; import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; @@ -150,11 +153,24 @@ public abstract class AbstractStreamEditor ext if(mShowPasswordCheckBox == null) { mShowPasswordCheckBox = new CheckBox("Show"); + mShowPasswordCheckBox.setAccessibleText("Show password"); } return mShowPasswordCheckBox; } + /** + * Links the password label to whichever password field (masked or unmasked) is currently visible, so that + * screen readers announce the label for the password field that has focus. + * @param label for the password fields + */ + protected void setPasswordLabel(Label label) + { + label.labelForProperty().bind(Bindings.when(getShowPasswordCheckBox().selectedProperty()) + .then((Node)getUnMaskedPasswordTextField()) + .otherwise(getMaskedPasswordTextField())); + } + protected IntegerTextField getPortTextField() { if(mPortTextField == null) diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyCallEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyCallEditor.java index 24330495..7918021d 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyCallEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyCallEditor.java @@ -22,6 +22,7 @@ package io.github.dsheirer.gui.playlist.streaming; import io.github.dsheirer.audio.broadcast.BroadcastServerType; import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallBroadcaster; import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.IntegerTextField; import io.github.dsheirer.playlist.PlaylistManager; import javafx.geometry.HPos; @@ -133,6 +134,7 @@ public class BroadcastifyCallEditor extends AbstractBroadcastEditor modifiedProperty().set(true)); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyStreamEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyStreamEditor.java index 01477fab..0484c97b 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyStreamEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/BroadcastifyStreamEditor.java @@ -22,6 +22,7 @@ package io.github.dsheirer.gui.playlist.streaming; import io.github.dsheirer.audio.broadcast.BroadcastServerType; import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.IntegerTextField; import io.github.dsheirer.playlist.PlaylistManager; import javafx.geometry.HPos; @@ -100,6 +101,7 @@ public class BroadcastifyStreamEditor extends AbstractStreamEditor modifiedProperty().set(true)); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/IcecastStreamEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/IcecastStreamEditor.java index 11fc08a6..dc4f4a05 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/IcecastStreamEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/IcecastStreamEditor.java @@ -24,6 +24,7 @@ package io.github.dsheirer.gui.playlist.streaming; import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.playlist.PlaylistManager; import javafx.geometry.HPos; import javafx.geometry.Insets; @@ -108,6 +109,7 @@ public abstract class IcecastStreamEditor extends AbstractStreamEditor modifiedProperty().set(true)); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/RdioScannerEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/RdioScannerEditor.java index a8207c6a..485b5ffe 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/RdioScannerEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/RdioScannerEditor.java @@ -131,6 +131,7 @@ public class RdioScannerEditor extends AbstractBroadcastEditor modifiedProperty().set(true)); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/ShoutcastV2StreamEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/ShoutcastV2StreamEditor.java index efd81738..e7787e87 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/ShoutcastV2StreamEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/ShoutcastV2StreamEditor.java @@ -25,6 +25,7 @@ package io.github.dsheirer.gui.playlist.streaming; import io.github.dsheirer.audio.broadcast.BroadcastServerType; import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration; +import io.github.dsheirer.gui.control.AccessibleToggleSwitch; import io.github.dsheirer.gui.control.IntegerTextField; import io.github.dsheirer.playlist.PlaylistManager; import javafx.geometry.HPos; @@ -113,6 +114,7 @@ public class ShoutcastV2StreamEditor extends AbstractStreamEditor modifiedProperty().set(true)); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamAliasSelectionEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamAliasSelectionEditor.java index 54838849..4cdd477f 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamAliasSelectionEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamAliasSelectionEditor.java @@ -39,6 +39,7 @@ import javafx.scene.control.SelectionMode; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; @@ -153,6 +154,7 @@ public class StreamAliasSelectionEditor extends VBox mSearchBox.setSpacing(5); Label searchLabel = new Label("Search:"); + searchLabel.setLabelFor(getSearchField()); searchLabel.setAlignment(Pos.CENTER_RIGHT); mSearchBox.getChildren().addAll(searchLabel, getSearchField()); } @@ -212,6 +214,7 @@ public class StreamAliasSelectionEditor extends VBox if(mAvailableAliasTableView == null) { mAvailableAliasTableView = new TableView<>(); + mAvailableAliasTableView.setAccessibleText("Available aliases"); mAvailableAliasTableView.setMaxHeight(Double.MAX_VALUE); mAvailableAliasTableView.setDisable(true); mAvailableAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); @@ -256,6 +259,7 @@ public class StreamAliasSelectionEditor extends VBox if(mSelectedAliasTableView == null) { mSelectedAliasTableView = new TableView<>(); + mSelectedAliasTableView.setAccessibleText("Aliases selected for this stream"); mSelectedAliasTableView.setMaxHeight(Double.MAX_VALUE); mSelectedAliasTableView.setDisable(true); mSelectedAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); @@ -303,6 +307,8 @@ public class StreamAliasSelectionEditor extends VBox mAddButton.setDisable(true); mAddButton.setMaxWidth(Double.MAX_VALUE); mAddButton.setGraphic(new IconNode(FontAwesome.ANGLE_RIGHT)); + mAddButton.setAccessibleText("Add selected alias to stream"); + mAddButton.setTooltip(new Tooltip("Add selected alias to stream")); mAddButton.setAlignment(Pos.CENTER); mAddButton.setOnAction(event -> { Alias selectedAlias = getAvailableAliasTableView().getSelectionModel().getSelectedItem(); @@ -327,6 +333,8 @@ public class StreamAliasSelectionEditor extends VBox mAddAllButton.setDisable(true); mAddAllButton.setMaxWidth(Double.MAX_VALUE); mAddAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_RIGHT)); + mAddAllButton.setAccessibleText("Add all selected aliases to stream"); + mAddAllButton.setTooltip(new Tooltip("Add all selected aliases to stream")); mAddAllButton.setAlignment(Pos.CENTER); mAddAllButton.setOnAction(new EventHandler() { @@ -361,6 +369,8 @@ public class StreamAliasSelectionEditor extends VBox mRemoveButton.setDisable(true); mRemoveButton.setMaxWidth(Double.MAX_VALUE); mRemoveButton.setGraphic(new IconNode(FontAwesome.ANGLE_LEFT)); + mRemoveButton.setAccessibleText("Remove selected alias from stream"); + mRemoveButton.setTooltip(new Tooltip("Remove selected alias from stream")); mRemoveButton.setAlignment(Pos.CENTER); mRemoveButton.setOnAction(event -> { Alias selectedAlias = getSelectedAliasTableView().getSelectionModel().getSelectedItem(); @@ -385,6 +395,8 @@ public class StreamAliasSelectionEditor extends VBox mRemoveAllButton.setDisable(true); mRemoveAllButton.setMaxWidth(Double.MAX_VALUE); mRemoveAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_LEFT)); + mRemoveAllButton.setAccessibleText("Remove all selected aliases from stream"); + mRemoveAllButton.setTooltip(new Tooltip("Remove all selected aliases from stream")); mRemoveAllButton.setAlignment(Pos.CENTER); mRemoveAllButton.setOnAction(event -> { List selectedAliases = diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java index 44bedc2e..65ea5dd6 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java @@ -396,6 +396,7 @@ public class StreamingEditor extends SplitPane if(mConfiguredBroadcastTableView == null) { mConfiguredBroadcastTableView = new TableView<>(); + mConfiguredBroadcastTableView.setAccessibleText("Streams"); mConfiguredBroadcastTableView.setPlaceholder(new Label("Click the New button to create a new " + "audio streaming configuration")); mConfiguredBroadcastTableView.setItems(mPlaylistManager.getBroadcastModel().getConfiguredBroadcasts()); @@ -414,12 +415,14 @@ public class StreamingEditor extends SplitPane if(empty || item == null || !item) { setGraphic(null); + setAccessibleText(empty ? null : "No"); } else { IconNode iconNode = new IconNode(FontAwesome.CHECK); iconNode.setFill(Color.GREEN); setGraphic(iconNode); + setAccessibleText("Yes"); } } }; diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/UnknownStreamEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/UnknownStreamEditor.java index 7aa7c1e5..0618f258 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/UnknownStreamEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/UnknownStreamEditor.java @@ -72,6 +72,7 @@ public class UnknownStreamEditor extends AbstractStreamEditor