diff --git a/.gitignore b/.gitignore index 3514e22cd0..6bf40bfd5a 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,7 @@ Modules/local # User configurations .qmlls.ini + +# Generated during build +Desktop/modules/activemodules.h +Tests/__pycache__/ diff --git a/Desktop/components/JASP/Widgets/AnalysisForms.qml b/Desktop/components/JASP/Widgets/AnalysisForms.qml index 1b0b9c0c54..3b432da2e4 100644 --- a/Desktop/components/JASP/Widgets/AnalysisForms.qml +++ b/Desktop/components/JASP/Widgets/AnalysisForms.qml @@ -6,6 +6,10 @@ import JASP.Controls FocusScope { id: analysisFormsFocusScope + + Accessible.role: Accessible.Pane + Accessible.name: qsTr("Analysis Forms") + implicitWidth: analysesModel.visible ? jaspTheme.formWidth + 1 + (2 * formsBackground.border.width) + verticalScrollbar.visibleBreadth : 0 Behavior on width { enabled: preferencesModel.animationsOn; PropertyAnimation { duration: jaspTheme.fileMenuSlideDuration; easing.type: Easing.OutCubic } } diff --git a/Desktop/components/JASP/Widgets/ComputeColumnWindow.qml b/Desktop/components/JASP/Widgets/ComputeColumnWindow.qml index 5b3e3ea2ac..ac89e5d82a 100644 --- a/Desktop/components/JASP/Widgets/ComputeColumnWindow.qml +++ b/Desktop/components/JASP/Widgets/ComputeColumnWindow.qml @@ -110,6 +110,10 @@ FocusScope textDocument: computeColumnEdit.textDocument } + Accessible.role: Accessible.EditableText + Accessible.name: qsTr("R code editor") + Accessible.description: qsTr("Enter R code for the computed column") + anchors.top: parent.top anchors.left: parent.left diff --git a/Desktop/components/JASP/Widgets/CreateComputeColumnDialog.qml b/Desktop/components/JASP/Widgets/CreateComputeColumnDialog.qml index 5994bb34a7..f4073d54c6 100644 --- a/Desktop/components/JASP/Widgets/CreateComputeColumnDialog.qml +++ b/Desktop/components/JASP/Widgets/CreateComputeColumnDialog.qml @@ -8,6 +8,9 @@ Popup id: popupCreateComputedColumn; modal: true; + Accessible.role: Accessible.Dialog + Accessible.name: qsTr("Create computed column dialog") + y: (parent.height / 2) - (height / 2) x: (parent.width / 2) - (width / 2) width: popupLoader.width @@ -144,6 +147,10 @@ Popup color: columnNameInUse ? jaspTheme.red : jaspTheme.black width: Math.max(implicitWidth, nameBox.width) selectByMouse: true + + Accessible.role: Accessible.EditableText + Accessible.name: qsTr("Computed column name") + Accessible.description: qsTr("Enter a name for the new computed column") ToolTip.visible: columnNameInUse ToolTip.text: qsTr("Column name is already used, please choose a different one.") diff --git a/Desktop/components/JASP/Widgets/CsvPreview.qml b/Desktop/components/JASP/Widgets/CsvPreview.qml index 84a309d37c..57627e14de 100644 --- a/Desktop/components/JASP/Widgets/CsvPreview.qml +++ b/Desktop/components/JASP/Widgets/CsvPreview.qml @@ -31,6 +31,9 @@ Window modality: Qt.ApplicationModal color: jaspTheme.white + Accessible.role: Accessible.Window + Accessible.name: title + property real windowPadding: 20 * jaspTheme.uiScale onVisibleChanged: diff --git a/Desktop/components/JASP/Widgets/CustomMenu.qml b/Desktop/components/JASP/Widgets/CustomMenu.qml index 9dba7db149..05f5bf8fb2 100644 --- a/Desktop/components/JASP/Widgets/CustomMenu.qml +++ b/Desktop/components/JASP/Widgets/CustomMenu.qml @@ -26,7 +26,10 @@ FocusScope id : menu width : menuRectangle.width height : menuRectangle.height - visible : showMe && (activeFocus || (hasSubMenus && customSubMenu.activeFocus)) + visible : showMe + Accessible.role : Accessible.Menu + Accessible.name : menuTitle + Accessible.ignored : !showMe x : Math.min(sourcePos.x + realOffsetX, sceneWidth - (width + 2) ) // Move the custom menu to the right if there is not enough space y : sourcePos.y + realOffsetY property var props : undefined @@ -44,11 +47,25 @@ FocusScope property bool menuMinIsMin: false // If set to true, this prevents the CustomMenu from going out of the scene by having a negative offset property point scrollOri : "0,0" //Just for other qmls to use as a general storage of the origin of their scrolling property bool isSubMenu : false + property string menuTitle : "Analysis menu" property int currentIndex: -1 + property bool _hadFocus: false + onSourceItemChanged: { menu.currentIndex = -1; } + onActiveFocusChanged: + { + if (activeFocus) + _hadFocus = true + else if (_hadFocus && showMe && !isSubMenu) + { + _hadFocus = false + closeMenu() + } + } + Connections { // As the sourcePos is calculated with the mapToItem function, there is no binding. So re-calculate the sourcePos each time the X or Y of the sourceItem changes. @@ -103,6 +120,7 @@ FocusScope { hasIcons = (menu.props === undefined || "undefined" === typeof(menu.props["hasIcons"])) ? true : menu.props["hasIcons"] hasSubMenus = (menu.props === undefined || "undefined" === typeof(menu.props["hasSubMenus"])) ? false : menu.props["hasSubMenus"] + menuTitle = (menu.props === undefined || "undefined" === typeof(menu.props["menuTitle"])) ? "Analysis menu" : menu.props["menuTitle"] if (menu.props === undefined || menu.props["model"] !== resultMenuModel) resultsJsInterface.runJavaScript("window.setSelection(false);") @@ -179,9 +197,9 @@ FocusScope function callMenuAction(index) { + menu.props['functionCall'](index) if (menu.sourceItem !== null) menu.sourceItem.forceActiveFocus() - menu.props['functionCall'](index) } function currentMenuItem(index) @@ -341,6 +359,10 @@ FocusScope property bool itemEnabled : menu.props.hasOwnProperty("enabled") ? menu.props["enabled"][index] : (model.modelData !== undefined || model.isEnabled) + Accessible.role : Accessible.MenuItem + Accessible.name : (model.modelData !== undefined ? model.modelData : displayText) + Accessible.onPressAction: { if (menuItem.itemEnabled) callMenuAction(index) } + Image { id : menuItemImage @@ -424,6 +446,10 @@ FocusScope property bool itemEnabled : menu.props.hasOwnProperty("enabled") ? menu.props["enabled"][index] : (model.modelData !== undefined || model.isEnabled) + Accessible.role : Accessible.MenuItem + Accessible.name : (model.modelData !== undefined ? model.modelData.substring(3) : displayText) + Accessible.onPressAction: { if (menuItem.itemEnabled) callMenuAction(index) } + Image { id : menuItemImage @@ -496,7 +522,12 @@ FocusScope Component { id : menuSeparator - ToolSeparator { orientation : Qt.Horizontal; width: column.columnWidth } + ToolSeparator { + orientation : Qt.Horizontal; + width: column.columnWidth + Accessible.role : Accessible.Separator + Accessible.name : "Separator" + } } } } diff --git a/Desktop/components/JASP/Widgets/DataPanel.qml b/Desktop/components/JASP/Widgets/DataPanel.qml index d9bc14e464..fd4f035305 100644 --- a/Desktop/components/JASP/Widgets/DataPanel.qml +++ b/Desktop/components/JASP/Widgets/DataPanel.qml @@ -6,6 +6,15 @@ import QtQuick Rectangle { id: rootDataset + + Accessible.role: Accessible.Pane + Accessible.name: qsTr("Data Panel") + + function focusDataTableView() + { + dataTableView.focusAndEdit(); + } + color: jaspTheme.uiBackground property int leftHandSpace: 0 //Used to allow splithandler to move out of the screen on the left a bit. @@ -50,6 +59,7 @@ Rectangle DataTableView { + id: dataTableView objectName: "dataSetTableView" SplitView.fillHeight: true onDoubleClicked: ribbonModel.showData() diff --git a/Desktop/components/JASP/Widgets/DataTableView.qml b/Desktop/components/JASP/Widgets/DataTableView.qml index d9bf85bb4c..3e3cd989a4 100644 --- a/Desktop/components/JASP/Widgets/DataTableView.qml +++ b/Desktop/components/JASP/Widgets/DataTableView.qml @@ -8,6 +8,24 @@ FocusScope { id: __myRoot + Accessible.role: Accessible.Table + Accessible.name: qsTr("Data Table View") + + Accessible.onPressAction: { focusAndEdit(); } + + JaspControls.ALTNavigation.enabled: true + JaspControls.ALTNavigation.requestedPostfix: "V" + JaspControls.ALTNavigation.onTagMatch: { focusAndEdit(); } + + function focusAndEdit() + { + __myRoot.forceActiveFocus(); + var col = dataTableView.view.selectionMin.x >= 0 ? dataTableView.view.selectionMin.x : 0; + var row = dataTableView.view.selectionMin.y >= 0 ? dataTableView.view.selectionMin.y : 0; + dataTableView.view.select(row, col, false, false); + dataTableView.view.edit(row, col); + } + signal doubleClicked() property alias isMainDataViewer: dataTableView.isMainDataViewer @@ -162,6 +180,7 @@ FocusScope "icons": menuIcons, "shortcut": menuShortcuts, "enabled": menuEnabled, + "menuTitle": qsTr("Context menu"), "functionCall": function (index) { menuFunctions[index](); diff --git a/Desktop/components/JASP/Widgets/DataTableViewColumnHeader.qml b/Desktop/components/JASP/Widgets/DataTableViewColumnHeader.qml index ee3d7acbc4..8b5d9bf875 100644 --- a/Desktop/components/JASP/Widgets/DataTableViewColumnHeader.qml +++ b/Desktop/components/JASP/Widgets/DataTableViewColumnHeader.qml @@ -13,6 +13,13 @@ Rectangle ? jaspTheme.itemSelectedNoFocusColor : jaspTheme.buttonColor + signal headerClicked() + + Accessible.role: Accessible.ColumnHeader + Accessible.name: qsTr("Column: %1").arg(headerText) + Accessible.description: columnDescription || "" + Accessible.onPressAction: { headerRoot.headerClicked(); } + readonly property int __iconDim: baseBlockDim * preferencesModel.uiScale function getColumnTypeIcon(type) @@ -66,34 +73,42 @@ Rectangle dataTableView.view.setColumnType(columnIndex, newColumnType) } - - MouseArea + function openTypeMenu() { - enabled: !virtual && computedColumnType !== computedColumnTypeAnalysis - anchors.fill: parent - onClicked: + var functionCall = function (index) { - var functionCall = function (index) - { - colIcon.setColumnType(columnTypesModel.getType(index)); - customMenu.hideMenus() - } + colIcon.setColumnType(columnTypesModel.getType(index)); + customMenu.hideMenus() + } - var props = { - "model": columnTypesModel, - "functionCall": functionCall - }; + var props = { + "model": columnTypesModel, + "functionCall": functionCall, + "menuTitle": qsTr("Column type menu for %1").arg(headerText) + }; - customMenu.scrollOri.x = dataTableView.contentX; - customMenu.scrollOri.y = 0; + customMenu.scrollOri.x = dataTableView.contentX; + customMenu.scrollOri.y = 0; - customMenu.toggle(headerRoot, props); + customMenu.toggle(headerRoot, props); - customMenu.menuScroll.x = Qt.binding(function() { return -1 * (dataTableView.contentX - customMenu.scrollOri.x); }); - customMenu.menuScroll.y = 0; - customMenu.menuMinIsMin = true - customMenu.sceneWidth = Qt.binding(function() { return dataTableView.width + dataTableView.x }) - } + customMenu.menuScroll.x = Qt.binding(function() { return -1 * (dataTableView.contentX - customMenu.scrollOri.x); }); + customMenu.menuScroll.y = 0; + customMenu.menuMinIsMin = true + customMenu.sceneWidth = Qt.binding(function() { return dataTableView.width + dataTableView.x }) + } + + + MouseArea + { + enabled: !virtual && computedColumnType !== computedColumnTypeAnalysis + anchors.fill: parent + onClicked: colIcon.openTypeMenu() + + Accessible.role: Accessible.PushButton + Accessible.name: qsTr("Change column type: %1").arg(headerText) + Accessible.description: qsTr("Open menu to change type for column %1").arg(headerText) + Accessible.onPressAction: { if (enabled) colIcon.openTypeMenu(); } hoverEnabled: true ToolTip.visible: containsMouse @@ -199,6 +214,7 @@ Rectangle if(columnIndex >= 0) { headerRoot.forceActiveFocus() + headerRoot.headerClicked() if(mouseEvent.button === Qt.LeftButton) { diff --git a/Desktop/components/JASP/Widgets/DataTableViewEdit.qml b/Desktop/components/JASP/Widgets/DataTableViewEdit.qml index 58e62bb971..0e66d3d09f 100644 --- a/Desktop/components/JASP/Widgets/DataTableViewEdit.qml +++ b/Desktop/components/JASP/Widgets/DataTableViewEdit.qml @@ -13,6 +13,15 @@ Item property bool keyPressed: false property alias text: editItem.text + Accessible.role: Accessible.Cell + Accessible.name: qsTr("Row %1, Col %2: %3 (editing)") + .arg(rowIndex + 1) + .arg(columnName || columnIndex + 1) + .arg(itemTextEdit || "") + Accessible.description: qsTr("Editing data cell at row %1, column %2") + .arg(rowIndex + 1) + .arg(columnName || columnIndex + 1) + TextInput { id: editItem @@ -27,10 +36,15 @@ Item anchors.fill: parent z: 10 - + + Accessible.role: Accessible.EditableText + Accessible.focusable: true + Accessible.name: "Edit cell value" + Keys.onPressed: (event) => { keyPressed = true + messages.log("DataTableViewEdit KEYS: key=" + event.key + " text='" + event.text + "' mod=" + event.modifiers) var rowI = rowIndex var colI = columnIndex diff --git a/Desktop/components/JASP/Widgets/DataTableViewItem.qml b/Desktop/components/JASP/Widgets/DataTableViewItem.qml index 7dfbde7d8d..55c2cbfa80 100644 --- a/Desktop/components/JASP/Widgets/DataTableViewItem.qml +++ b/Desktop/components/JASP/Widgets/DataTableViewItem.qml @@ -7,6 +7,15 @@ Item { id: itemDelegateItem property bool showShadow: itemShadowText !== undefined && itemText !== undefined && itemText !== itemShadowText + + Accessible.role: Accessible.Cell + Accessible.name: qsTr("Row %1, Col %2: %3") + .arg(rowIndex + 1) + .arg(columnName || columnIndex + 1) + .arg(itemText || "") + Accessible.description: qsTr("Data cell at row %1, column %2") + .arg(rowIndex + 1) + .arg(columnName || columnIndex + 1) TextMetrics { diff --git a/Desktop/components/JASP/Widgets/DataTableViewRowHeader.qml b/Desktop/components/JASP/Widgets/DataTableViewRowHeader.qml index 1e9d774ed8..d257bdfb67 100644 --- a/Desktop/components/JASP/Widgets/DataTableViewRowHeader.qml +++ b/Desktop/components/JASP/Widgets/DataTableViewRowHeader.qml @@ -9,6 +9,11 @@ Rectangle color: ribbonModel.dataMode && (dataTableView.view.selectionMin.y <= rowIndex && dataTableView.view.selectionMax.y >= rowIndex) ? jaspTheme.itemSelectedNoFocusColor : jaspTheme.buttonColor + + Accessible.role: Accessible.RowHeader + Accessible.name: qsTr("Row %1").arg(rowNumber + 1) + Accessible.description: qsTr("Row header for row %1. Click to select, shift for multi-select.") + .arg(rowNumber + 1) Text { text: rowNumber diff --git a/Desktop/components/JASP/Widgets/EncryptionSettingsWindow.qml b/Desktop/components/JASP/Widgets/EncryptionSettingsWindow.qml index 7389c82b8c..a39acc8b80 100644 --- a/Desktop/components/JASP/Widgets/EncryptionSettingsWindow.qml +++ b/Desktop/components/JASP/Widgets/EncryptionSettingsWindow.qml @@ -5,6 +5,8 @@ Window { id: encryptWindow + Accessible.name: title + minimumWidth: Math.max(passwordInput.minWidth, jaspSubmission.implicitWidth, showAdvancedCheckbox.implicitWidth) + windowPadding * 2 + 150 * jaspTheme.uiScale minimumHeight: contentColumn.implicitHeight + (advancedSettings.visible ? advancedSettings.implicitHeight + jaspTheme.groupContentPadding : 0) + buttons.height + windowPadding * 3 visible: encryptionModel.visible diff --git a/Desktop/components/JASP/Widgets/EnginesWindow.qml b/Desktop/components/JASP/Widgets/EnginesWindow.qml index 7cf7098853..fc3a083da5 100644 --- a/Desktop/components/JASP/Widgets/EnginesWindow.qml +++ b/Desktop/components/JASP/Widgets/EnginesWindow.qml @@ -7,6 +7,9 @@ import JASP.Controls Window { id: enginesWindow + + Accessible.name: title + width: 400 * jaspTheme.uiScale height: Screen.height minimumWidth: 200 * preferencesModel.uiScale diff --git a/Desktop/components/JASP/Widgets/ErrorMessage.qml b/Desktop/components/JASP/Widgets/ErrorMessage.qml index cb2bd6df1b..cd7f3a0216 100644 --- a/Desktop/components/JASP/Widgets/ErrorMessage.qml +++ b/Desktop/components/JASP/Widgets/ErrorMessage.qml @@ -24,6 +24,10 @@ import JASP.Controls Rectangle { id : errorMsg + + Accessible.role: Accessible.StaticText + Accessible.name: text + color : warning ? jaspTheme.controlWarningBackgroundColor : jaspTheme.controlErrorBackgroundColor visible : opacity > 0 && text != "" opacity : 1 diff --git a/Desktop/components/JASP/Widgets/FileMenu/FileMenu.qml b/Desktop/components/JASP/Widgets/FileMenu/FileMenu.qml index 36614a0889..abe91a0172 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/FileMenu.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/FileMenu.qml @@ -10,6 +10,9 @@ FocusScope { id: fileMenu + Accessible.role: Accessible.Pane + Accessible.name: qsTr("File menu") + Keys.onEscapePressed: { ribbon.focus = true; diff --git a/Desktop/components/JASP/Widgets/FileMenu/ListItem.qml b/Desktop/components/JASP/Widgets/FileMenu/ListItem.qml index bcf6764dde..85a6a7a626 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/ListItem.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/ListItem.qml @@ -7,6 +7,8 @@ FocusScope width: 300 //Should be set from ListView height: rectTitle.height + rectDescription.height + 3 + + readonly property int folderModelType: 3 property alias color: rectTitleBackground.color property alias border: rectTitleBackground.border @@ -33,17 +35,18 @@ FocusScope onAllHoveredChanged: if(allHovered) { ListView.currentIndex = index; forceActiveFocus(); } - Keys.onEnterPressed: openStuff(model); - Keys.onReturnPressed: (event)=> openStuff(model); - Keys.onSpacePressed: openStuff(model); - Keys.onRightPressed: if(model.type === 3) openStuff(model); + Keys.onEnterPressed: openStuff(model); + Keys.onReturnPressed: openStuff(model); + Keys.onSpacePressed: openStuff(model); + Keys.onRightPressed: if(model.type === folderModelType) openStuff(model); + function openStuff(model) { if (!rectTitleAndDescripton.cppModel.mayOpen()) return; - if (model.type === 3) rectTitleAndDescripton.cppModel.changePath(model.name, model.path); //Folder type - else rectTitleAndDescripton.cppModel.openFile(model.path) + if (model.type === folderModelType) rectTitleAndDescripton.cppModel.changePath(model.name, model.path); //Folder type + else rectTitleAndDescripton.cppModel.openFile(model.path) } Rectangle @@ -57,6 +60,11 @@ FocusScope anchors.margins: 1 radius: jaspTheme.borderRadius + Accessible.role: Accessible.Button + Accessible.name: (model.type == folderModelType ? qsTr("Folder %1") : qsTr("File %1")).arg(model.name) + Accessible.description: Accessible.name + Accessible.onPressAction: openStuff(model) + color: rectTitleAndDescripton.allPressed || (rectTitleAndDescripton.activeFocus && !datafileMouseArea.containsMouse && !rectTitleAndDataFile.activeFocus) ? jaspTheme.buttonColorPressed : rectTitleAndDescripton.allHovered @@ -66,11 +74,11 @@ FocusScope { id : firstFileOrFolderImage - height: model.type==3 ? 0.75 * rectTitle.height : 0.95 * rectTitle.height //Tune folder image to file image (wtih topmargin in svg) + height: model.type==folderModelType ? 0.75 * rectTitle.height : 0.95 * rectTitle.height //Tune folder image to file image (wtih topmargin in svg) width: height anchors.left: rectTitle.left anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: model.type==3 ? 5 * preferencesModel.uiScale : 0 + anchors.leftMargin: model.type==folderModelType ? 5 * preferencesModel.uiScale : 0 fillMode: Image.PreserveAspectFit source: model.iconsource @@ -123,6 +131,13 @@ FocusScope Keys.onSpacePressed: (event) => { openDataFile(event); } onActiveFocusChanged: if (!activeFocus) focus = false; + + Accessible.role: Accessible.Button + Accessible.name: qsTr("Datafile %1").arg(model.name) + Accessible.description: qsTr("Datafile %1").arg(model.name) + Accessible.onPressAction: if(hasDataFile) openDataFile(); + Accessible.ignored: !hasDatafile + function openDataFile(event) { diff --git a/Desktop/components/JASP/Widgets/FileMenu/MenuHeader.qml b/Desktop/components/JASP/Widgets/FileMenu/MenuHeader.qml index 8138fedd3e..651dd716b3 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/MenuHeader.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/MenuHeader.qml @@ -7,6 +7,9 @@ import JASP.Controls Item { + Accessible.role: Accessible.StaticText + Accessible.name: headertext + property bool toolseparator: true property string headertext: "" property string helpfile: "" @@ -40,6 +43,8 @@ Item HelpButton { id: idHelpButton + text: qsTr("Help") + visible: helpfile !== "" height: parent.height - (jaspTheme.generalAnchorMargin * 2) width: height buttonPadding: 6 * preferencesModel.uiScale diff --git a/Desktop/components/JASP/Widgets/FileMenu/PrefsGroupRect.qml b/Desktop/components/JASP/Widgets/FileMenu/PrefsGroupRect.qml index 6079c110b2..c11e272035 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/PrefsGroupRect.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/PrefsGroupRect.qml @@ -7,6 +7,9 @@ import JASP Rectangle { + Accessible.role: Accessible.Section + Accessible.name: title + color: "transparent" border.color: jaspTheme.fileMenuLightBorder border.width: 1 diff --git a/Desktop/components/JASP/Widgets/FilterWindow.qml b/Desktop/components/JASP/Widgets/FilterWindow.qml index b5e6e01eb5..a68998439e 100644 --- a/Desktop/components/JASP/Widgets/FilterWindow.qml +++ b/Desktop/components/JASP/Widgets/FilterWindow.qml @@ -8,6 +8,9 @@ FocusScope { id: filterContainer + Accessible.role: Accessible.Pane + Accessible.name: qsTr("Filter Window") + function close() { diff --git a/Desktop/components/JASP/Widgets/HelpWindow.qml b/Desktop/components/JASP/Widgets/HelpWindow.qml index dc3265447c..4ed501f18a 100644 --- a/Desktop/components/JASP/Widgets/HelpWindow.qml +++ b/Desktop/components/JASP/Widgets/HelpWindow.qml @@ -37,6 +37,7 @@ Window WebEngineView { id: helpView + Accessible.name: qsTr("JASP Help") url: helpModel.indexURL() anchors.fill: parent anchors.bottomMargin: searchBar.height + (jaspTheme.generalAnchorMargin * 2) diff --git a/Desktop/components/JASP/Widgets/JASPDataView.qml b/Desktop/components/JASP/Widgets/JASPDataView.qml index dfb9dd77c8..d03b633d39 100644 --- a/Desktop/components/JASP/Widgets/JASPDataView.qml +++ b/Desktop/components/JASP/Widgets/JASPDataView.qml @@ -88,6 +88,7 @@ FocusScope Keys.onPressed: (event)=> { var controlPressed = Boolean(event.modifiers & Qt.ControlModifier); + messages.log("JASPDataView KEYS: key=" + event.key + " text='" + event.text + "'") if(controlPressed) switch(event.key) diff --git a/Desktop/components/JASP/Widgets/JASPSplitHandle.qml b/Desktop/components/JASP/Widgets/JASPSplitHandle.qml index 2d165534f2..4a4b515008 100644 --- a/Desktop/components/JASP/Widgets/JASPSplitHandle.qml +++ b/Desktop/components/JASP/Widgets/JASPSplitHandle.qml @@ -8,6 +8,9 @@ Rectangle { id: handleRoot + Accessible.role: Accessible.Slider + Accessible.name: qsTr("Split handle") + signal arrowClicked signal handleDragging(bool active, var mouseArea) property bool pointingLeft: true diff --git a/Desktop/components/JASP/Widgets/MainPage.qml b/Desktop/components/JASP/Widgets/MainPage.qml index cfe0ca1705..389fbb6e24 100644 --- a/Desktop/components/JASP/Widgets/MainPage.qml +++ b/Desktop/components/JASP/Widgets/MainPage.qml @@ -27,6 +27,9 @@ Item { id: splitViewContainer + Accessible.role: Accessible.Pane + Accessible.name: qsTr("Main Page") + // The MainPage has 3 panels: Data, Analyses form and Results. // There are 3 configurations: only data, only analyses with results or all 3 panels. // Between these 3 panels, there are 2 handles: handleBetweenDataAndAnalyses & handleBetweenAnalysesAndResults: if there are only data @@ -54,6 +57,12 @@ Item handleDataAnalyses.x = splitViewContainer.width - (hasAnalysis ? handleAnalysesResults.width : 0) } + function changeFocusToDataViewer() + { + maximizeDataPanel(); + dataPanel.focusDataTableView(); + } + Connections { target: analysesModel @@ -315,6 +324,11 @@ Item clip: true anchors.fill: parent anchors.leftMargin: 1 + + Accessible.role: Accessible.WebDocument + Accessible.name: qsTr("Results") + Accessible.description: qsTr("Results") + url: resultsJsInterface.resultsPageUrl diff --git a/Desktop/components/JASP/Widgets/MainWindow.qml b/Desktop/components/JASP/Widgets/MainWindow.qml index dc94dffe69..e22a1a607b 100644 --- a/Desktop/components/JASP/Widgets/MainWindow.qml +++ b/Desktop/components/JASP/Widgets/MainWindow.qml @@ -23,8 +23,11 @@ import QtQuick.Controls Window { - id: mainWindowRoot - title: mainWindow.windowTitle + id: mainWindowRoot + + Accessible.name: mainWindow.windowTitle + + title: mainWindow.windowTitle visible: true width: 1280 height: 720 @@ -84,6 +87,11 @@ Window ribbon.showFileMenuPressed(); } + function changeFocusToDataViewer() + { + mainpage.changeFocusToDataViewer(); + } + function mod (a, n) { return (a + n) % n; diff --git a/Desktop/components/JASP/Widgets/MessageBox.qml b/Desktop/components/JASP/Widgets/MessageBox.qml index b62cf3f308..c888dfb618 100644 --- a/Desktop/components/JASP/Widgets/MessageBox.qml +++ b/Desktop/components/JASP/Widgets/MessageBox.qml @@ -8,6 +8,9 @@ QtC.Popup { id: messageRoot + Accessible.role: Accessible.Dialog + Accessible.name: title.text + x: (mainWindowRoot.width - width) / 2 y: (mainWindowRoot.height - height) / 2 diff --git a/Desktop/components/JASP/Widgets/ModulesMenu.qml b/Desktop/components/JASP/Widgets/ModulesMenu.qml index 09b18ed5a2..8b0646b51d 100644 --- a/Desktop/components/JASP/Widgets/ModulesMenu.qml +++ b/Desktop/components/JASP/Widgets/ModulesMenu.qml @@ -10,6 +10,9 @@ FocusScope { id: modulesMenu + Accessible.role: Accessible.Pane + Accessible.name: qsTr("Modules menu") + width: slidePart.width height: 600 z: 1 diff --git a/Desktop/components/JASP/Widgets/RCommanderWindow.qml b/Desktop/components/JASP/Widgets/RCommanderWindow.qml index f57a4beea6..1d606d93ef 100644 --- a/Desktop/components/JASP/Widgets/RCommanderWindow.qml +++ b/Desktop/components/JASP/Widgets/RCommanderWindow.qml @@ -25,6 +25,9 @@ import JASP.Controls as JC Window { id: rcmdRoot + + Accessible.name: title + title: qsTr("R in JASP") visible: true width: 800 * preferencesModel.uiScale diff --git a/Desktop/components/JASP/Widgets/RenameColumnDialog.qml b/Desktop/components/JASP/Widgets/RenameColumnDialog.qml index 26b11929d5..e4f8b8bd23 100644 --- a/Desktop/components/JASP/Widgets/RenameColumnDialog.qml +++ b/Desktop/components/JASP/Widgets/RenameColumnDialog.qml @@ -7,6 +7,10 @@ Popup id: popupRenameColumnDialog; modal: true; + Accessible.role: Accessible.Dialog + Accessible.name: qsTr("Rename column dialog") + Accessible.description: qsTr("Rename column to a new name") + y: (parent.height / 2) - (height / 2) x: (parent.width / 2) - (width / 2) width: popupLoader.width @@ -74,6 +78,10 @@ Popup color: jaspTheme.textEnabled font: jaspTheme.fontGroupTitle selectByMouse: true + + Accessible.role: Accessible.EditableText + Accessible.name: qsTr("New column name") + Accessible.description: qsTr("Enter a new name for the column") anchors { top: title.bottom diff --git a/Desktop/components/JASP/Widgets/Ribbon/MenuArrowButton.qml b/Desktop/components/JASP/Widgets/Ribbon/MenuArrowButton.qml index ce29c579ec..80bf584fe7 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/MenuArrowButton.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/MenuArrowButton.qml @@ -49,6 +49,10 @@ Rectangle readonly property bool showArrow: buttonType == MenuArrowButton.ButtonType.LeftArrow || buttonType == MenuArrowButton.ButtonType.RightArrow readonly property bool isTools: buttonType == MenuArrowButton.ButtonType.Tools + Accessible.role: Accessible.Button + Accessible.name: hamburger ? qsTr("Main menu") : !isTools ? qsTr("Modules menu") : qsTr("Workspace tools") + Accessible.description: toolTip != "" ? toolTip : Accessible.name + Accessible.onPressAction: clicked() ToolTip.text: toolTip ToolTip.visible: toolTip !== "" && mice.containsMouse @@ -59,7 +63,7 @@ Rectangle { id: hamburgerArrow anchors.centerIn: parent - width: hamburgerArrow.barWidth//parent.width - (2 * jaspTheme.ribbonButtonPadding) + width: hamburgerArrow.barWidth height: baseHeight - 20 scale: baseScale * (mice.containsMouse && !ribbonButton.pressed ? jaspTheme.ribbonScaleHovered : 1) diff --git a/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml b/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml index d940128a8f..43fb3f05df 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml @@ -24,6 +24,10 @@ import JASP.Controls FocusScope { id : ribbonBar + + Accessible.role: Accessible.ToolBar + Accessible.name: qsTr("Ribbon Bar") + height : ribbonMenu.height // This property is required to show filemenu button press in KeyNavigation diff --git a/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml b/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml index e10ecb300e..68f5caccde 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml @@ -42,6 +42,12 @@ Item property var menu : [] property bool myMenuOpen : false property bool showPressed : ribbonButton.activeFocus || myMenuOpen + + Accessible.role: Accessible.Button + Accessible.name: text + Accessible.description: toolTip + Accessible.onPressAction: startOrShowMenu() + onMyMenuOpenChanged: if(!myMenuOpen) myMenuOpen = false; //Break the binding @@ -174,7 +180,8 @@ Item "hasIcons" : ribbonButton.menu.hasIcons(), "hasSubMenus" : ribbonButton.menu.hasSubMenus(), "navigateFunc" : navigateFunc, - "parentNavigateFunc" : parentNavigateFunc + "parentNavigateFunc" : parentNavigateFunc, + "menuTitle" : ribbonButton.text + " menu" }; customMenu.toggle(ribbonButton, props); diff --git a/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml b/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml index 8b20c10c4e..77ead0ed20 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml @@ -24,6 +24,10 @@ import JASP Item { id: jaspRibbons + + Accessible.role: Accessible.List + Accessible.name: qsTr("Module ribbons") + objectName: "jaspRibbon" width: 500 height: jaspTheme.ribbonButtonHeight * preferencesModel.ribbonBarHeightScale diff --git a/Desktop/components/JASP/Widgets/SpinBox.qml b/Desktop/components/JASP/Widgets/SpinBox.qml index 65c4b4f38c..f6dc3c0f2f 100644 --- a/Desktop/components/JASP/Widgets/SpinBox.qml +++ b/Desktop/components/JASP/Widgets/SpinBox.qml @@ -38,6 +38,8 @@ Item property double stepSize: 1 property alias text: label.text property string toolTip: "" + property string info: "" + property string title: "" property alias implicitWidthLabel: label.implicitWidth property alias widthLabel: label.width @@ -50,6 +52,15 @@ Item Keys.onReturnPressed: (event)=> { valueField.focus = !valueField.focus; } activeFocusOnTab: true + + Accessible.role: Accessible.SpinBox + Accessible.name: text + Accessible.description: info === undefined || info == "" ? toolTip !== undefined && toolTip != "" ? toolTip : qsTr("Spinbox %1").arg(title) : info + Accessible.onIncreaseAction: plus.clicked() + Accessible.onDecreaseAction: minus.clicked() + + + signal editingFinished() diff --git a/Desktop/components/JASP/Widgets/VariablesWindow.qml b/Desktop/components/JASP/Widgets/VariablesWindow.qml index 6f387ac39e..743d07883f 100644 --- a/Desktop/components/JASP/Widgets/VariablesWindow.qml +++ b/Desktop/components/JASP/Widgets/VariablesWindow.qml @@ -29,6 +29,10 @@ import "./FileMenu" FocusScope { id: variablesContainer + + Accessible.role: Accessible.Pane + Accessible.name: qsTr("Variables Window") + visible: columnModel.visible property real calculatedBaseHeight: (columnInfoTop.height + jaspTheme.generalAnchorMargin * 2) @@ -144,6 +148,11 @@ FocusScope required property int index + Accessible.role: Accessible.PageTab + Accessible.name: columnModel.tabs[index].title + Accessible.description: qsTr("Switch to %1 tab").arg(columnModel.tabs[index].title) + Accessible.onPressAction: { tabbar.currentIndex = index; } + onCheckedChanged: if (checked) tabView.currentTabButton = tabButton; diff --git a/Desktop/components/JASP/Widgets/WavyWindow.qml b/Desktop/components/JASP/Widgets/WavyWindow.qml index 70fee56ea7..2a40250c16 100644 --- a/Desktop/components/JASP/Widgets/WavyWindow.qml +++ b/Desktop/components/JASP/Widgets/WavyWindow.qml @@ -10,6 +10,9 @@ Window { id: aboutWindow + Accessible.role: Accessible.Window + Accessible.name: title + width: 1200 * preferencesModel.uiScale height: 600 * preferencesModel.uiScale @@ -153,6 +156,9 @@ Window Keys.onEnterPressed: (event) => { closeModel() } Keys.onReturnPressed: (event) => { closeModel() } Keys.onEscapePressed: (event) => { closeModel() } + + Accessible.role: Accessible.Button + Accessible.name: qsTr("Close") } } diff --git a/Desktop/components/JASP/Widgets/WelcomePage.qml b/Desktop/components/JASP/Widgets/WelcomePage.qml index a129f55307..42a821b4f7 100644 --- a/Desktop/components/JASP/Widgets/WelcomePage.qml +++ b/Desktop/components/JASP/Widgets/WelcomePage.qml @@ -25,6 +25,9 @@ FocusScope { id: welcomeRoot + Accessible.role: Accessible.Client + Accessible.name: qsTr("Welcome Page") + property real scaler: Math.max(0.85, Math.min(2, 0.85 * Math.min(welcomeRoot.width / info.baseWidth, welcomeRoot.height / info.baseHeight))) Rectangle diff --git a/Desktop/html/css/jasp.css b/Desktop/html/css/jasp.css index 9313e9cc17..cc54575617 100644 --- a/Desktop/html/css/jasp.css +++ b/Desktop/html/css/jasp.css @@ -636,3 +636,44 @@ iframe.ql-video { transform: translateX(0); } } + +/* Accessibility enhancements */ +/* Ensure table headers are properly labeled */ +th[role="columnheader"], +th[role="rowheader"] { + font-weight: bold; +} + +/* Focus indicators for keyboard navigation on ARIA-interactive results */ +[role="table"]:focus, +[role="tablist"]:focus, +[role="tab"]:focus, +[role="button"]:focus, +[role="menuitem"]:focus, +[role="link"]:focus, +[role="gridcell"]:focus { + outline: 2px solid #0066cc; + outline-offset: 2px; +} + +/* High contrast focus indicators */ +@media (prefers-contrast: high) { + [role="button"]:focus, + [role="menuitem"]:focus, + [role="tab"]:focus { + outline: 3px solid #000; + outline-offset: 2px; + } +} + +/* Screen reader only content */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} diff --git a/Desktop/html/js/jaspNotes.js b/Desktop/html/js/jaspNotes.js index f5536e28f9..33bfd9ed33 100644 --- a/Desktop/html/js/jaspNotes.js +++ b/Desktop/html/js/jaspNotes.js @@ -231,6 +231,9 @@ JASPWidgets.NoteBox = JASPWidgets.View.extend({ var self = this; this.closeButton.actionTargetElement = function () { self.closeButton.$el.attr("title", i18n("Remove this note")) + .attr("role", "button") + .attr("tabindex", "0") + .attr("aria-label", i18n("Remove this note")) .tooltip({position: {my:"center bottom-15", at:"center top"}}); return self.$el; }; @@ -298,11 +301,13 @@ JASPWidgets.NoteBox = JASPWidgets.View.extend({ this.setVisibility(this.visible) var html = this.model.get("text"); + var noteId = 'note-' + Math.random().toString(36).substr(2, 9); this.closeButton.render(); this.$el.append(`
`); - this.$el.append(``) + this.$el.attr('role', 'region').attr('aria-label', i18n('Note')).attr('id', noteId) + this.$el.append(`