desktop/source/lib/init.cxx | 2 sfx2/source/view/viewsh.cxx | 135 ++++++++++++++++++++--- svx/source/accessibility/ChildrenManagerImpl.cxx | 8 - 3 files changed, 126 insertions(+), 19 deletions(-)
New commits: commit c34f465a21dab492afec12011f4a16584a663d9f Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Tue Oct 3 12:34:45 2023 +0200 Commit: Caolán McNamara <caolan.mcnam...@collabora.com> CommitDate: Sun Oct 22 16:39:59 2023 +0200 lok: a11y: impress: screen reader support for text shape editing Now accessibility support can be enabled in Impress. Fixed an issue that prevented to receive accessibility event when a shape text content was edited. Some rectangles overlapping check is performed for excluding not visible shapes from the a11y tree Anyway client and core visible area can differ some shape was wrongly pruned from the a11y tree. The problem has been fixed by not performing the overlapping test in the LOK case: we already do the same for shape area invalidation. In order to avoid the screen reader to report no more focused paragraph we send an empty paragraph to clear the editable area when shape selection state changes. When shape ediding becomes active the text content of the shape is sent to the client. Change-Id: Ia9ee08d060162891725d26efd2aa36e47b38ed50 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/157525 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158329 Tested-by: Jenkins Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index bbb32dd3d74f..57869a7540ef 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -7293,7 +7293,7 @@ static void doc_setAccessibilityState(SAL_UNUSED_PARAMETER LibreOfficeKitDocumen SolarMutexGuard aGuard; int nDocType = getDocumentType(pThis); - if (nDocType != LOK_DOCTYPE_TEXT) + if (!(nDocType == LOK_DOCTYPE_TEXT || nDocType == LOK_DOCTYPE_PRESENTATION)) return; SfxLokHelper::setAccessibilityState(nId, nEnabled); diff --git a/sfx2/source/view/viewsh.cxx b/sfx2/source/view/viewsh.cxx index 68c99691fb4c..34edb460d4e3 100644 --- a/sfx2/source/view/viewsh.cxx +++ b/sfx2/source/view/viewsh.cxx @@ -271,6 +271,28 @@ bool isFocused(const accessibility::AccessibleEventObject& aEvent) return hasState(aEvent, accessibility::AccessibleStateType::FOCUSED); } +sal_Int16 getParentRole(const uno::Reference<accessibility::XAccessible>& xAccObject) +{ + if (!xAccObject.is()) + return 0; + + uno::Reference<accessibility::XAccessibleContext> xContext(xAccObject, uno::UNO_QUERY); + if (xContext.is()) + { + uno::Reference<accessibility::XAccessible> xParent = xContext->getAccessibleParent(); + if (xParent.is()) + { + uno::Reference<accessibility::XAccessibleContext> xParentContext(xParent, + uno::UNO_QUERY); + if (xParentContext.is()) + { + return xParentContext->getAccessibleRole(); + } + } + } + return 0; +} + // Put in rAncestorList all ancestors of xTable up to xAncestorTable or // up to the first not-a-table ancestor if xAncestorTable is not an ancestor. // xTable is included in the list, xAncestorTable is not included. @@ -364,6 +386,16 @@ void aboutEvent(std::string msg, const accessibility::AccessibleEventObject& aEv << "\n parent: " << xContext->getAccessibleParent().get() << "\n child count: " << xContext->getAccessibleChildCount()); } + else + { + SAL_INFO("lok.a11y", msg << ": event id: " << aEvent.EventId + << ", no accessible context!"); + } + } + else + { + SAL_INFO("lok.a11y", msg << ": event id: " << aEvent.EventId + << ", no accessible source!"); } uno::Reference< accessibility::XAccessible > xOldValue; aEvent.OldValue >>= xOldValue; @@ -690,6 +722,7 @@ private: bool force, std::string msg = ""); void updateAndNotifyParagraph(const uno::Reference<css::accessibility::XAccessibleText>& xAccText, bool force, std::string msg = ""); + void resetParagraphInfo(); }; LOKDocumentFocusListener::LOKDocumentFocusListener(const SfxViewShell* pViewShell) @@ -891,6 +924,20 @@ bool LOKDocumentFocusListener::updateParagraphInfo(const uno::Reference<css::acc m_nSelectionEnd = xAccText->getSelectionEnd(); m_nListPrefixLength = getListPrefixSize(xAccText); + // Inside a text shape when there is no selection, selection-start and selection-end are + // set to current caret position instead of -1. Moreover, inside a text shape pressing + // delete or backspace with an empty selection really deletes text and not only the empty + // selection as it occurs in a text paragraph in Writer. + // So whenever selection-start == selection-end, and we are inside a shape we need + // to set these parameters to -1 in order to have the client to handle delete and + // backspace properly. + if (m_nSelectionStart == m_nSelectionEnd && m_nSelectionStart != -1) + { + uno::Reference<accessibility::XAccessible> xAccObject(xAccText, uno::UNO_QUERY); + if (getParentRole(xAccObject) == accessibility::AccessibleRole::SHAPE) + m_nSelectionStart = m_nSelectionEnd = -1; + } + // In case only caret position or text selection are different we can rely on specific events. if (m_sFocusedParagraph != sText) { @@ -920,6 +967,15 @@ void LOKDocumentFocusListener::updateAndNotifyParagraph( notifyFocusedParagraphChanged(force); } +void LOKDocumentFocusListener::resetParagraphInfo() +{ + m_sFocusedParagraph = ""; + m_nCaretPosition = 0; + m_nSelectionStart = -1; + m_nSelectionEnd = -1; + m_nListPrefixLength = 0; +} + void LOKDocumentFocusListener::notifyEvent(const accessibility::AccessibleEventObject& aEvent ) { aboutView("LOKDocumentFocusListener::notifyEvent", this, m_pViewShell); @@ -935,10 +991,20 @@ void LOKDocumentFocusListener::notifyEvent(const accessibility::AccessibleEventO uno::Reference< accessibility::XAccessible > xAccessibleObject = getAccessible(aEvent); sal_Int64 nState = accessibility::AccessibleStateType::INVALID; aEvent.NewValue >>= nState; + sal_Int64 nOldState = accessibility::AccessibleStateType::INVALID; + aEvent.OldValue >>= nOldState; SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: " - "STATE_CHANGED: nState: " << stateSetToString(nState)); + "STATE_CHANGED: nNewState: " << stateSetToString(nState) + << " , STATE_CHANGED: nOldState: " << stateSetToString(nOldState)); - if( accessibility::AccessibleStateType::FOCUSED == nState ) + if (accessibility::AccessibleStateType::ACTIVE == nState && + getParentRole(xAccessibleObject) == accessibility::AccessibleRole::SHAPE) + { + uno::Reference<css::accessibility::XAccessibleText> xAccText(xAccessibleObject, uno::UNO_QUERY); + updateParagraphInfo(xAccText, true, "STATE_CHANGED: ACTIVE"); + notifyFocusedParagraphChanged(true); + } + else if( accessibility::AccessibleStateType::FOCUSED == nState ) { SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: FOCUSED"); uno::Reference<css::accessibility::XAccessibleText> xAccText(xAccessibleObject, uno::UNO_QUERY); @@ -1090,14 +1156,8 @@ void LOKDocumentFocusListener::notifyEvent(const accessibility::AccessibleEventO // So we could need to notify a new focused paragraph changed message. if (!isFocused(aEvent)) { - OUString sText = xAccText->getText(); - if (m_sFocusedParagraph != sText) - { - m_sFocusedParagraph = sText; - m_nSelectionStart = xAccText->getSelectionStart(); - m_nSelectionEnd = xAccText->getSelectionEnd(); - notifyFocusedParagraphChanged(true); - } + if (updateParagraphInfo(xAccText, false, "CARET_CHANGED")) + notifyFocusedParagraphChanged(true); } else { @@ -1150,9 +1210,7 @@ void LOKDocumentFocusListener::notifyEvent(const accessibility::AccessibleEventO // if a text selection object exists or not. That's needed because of the odd behavior // occurring when <backspace>/<delete> are hit and a text selection is empty but it still exists. // Such keys delete the empty selection instead of the previous/next char. - m_nSelectionStart = xAccText->getSelectionStart(); - m_nSelectionEnd = xAccText->getSelectionEnd(); - m_nCaretPosition = xAccText->getCaretPosition(); + updateParagraphInfo(xAccText, false, "TEXT_SELECTION_CHANGED"); // Calc: when editing a formula send the update content if (m_bIsEditingCell && !m_sSelectedCellAddress.isEmpty() @@ -1181,8 +1239,24 @@ void LOKDocumentFocusListener::notifyEvent(const accessibility::AccessibleEventO { OUString sName = xContext->getAccessibleName(); SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: SELECTION_CHANGED: this: " << this - << ", selected cell address: >" << sName << "<" - ", m_bIsEditingCell: " << m_bIsEditingCell); + << ", selected object: >" << sName << "<" + ", m_bIsEditingCell: " << m_bIsEditingCell); + if (xContext->getAccessibleRole() == accessibility::AccessibleRole::SHAPE) + { + if (xContext->getAccessibleChildCount() > 0) + { + uno::Reference<accessibility::XAccessible> xAccChild = + xContext->getAccessibleChild(0); + uno::Reference<css::accessibility::XAccessibleText> xAccText(xAccChild, uno::UNO_QUERY); + if (xAccText.is()) + { + // At present when a shape is selected screen reader reports editable area content + // on caret navigation even if shape editing is not active + resetParagraphInfo(); + notifyFocusedParagraphChanged(true); + } + } + } if (m_bIsEditingCell && !sName.isEmpty()) { m_sSelectedCellAddress = sName; @@ -1203,6 +1277,37 @@ void LOKDocumentFocusListener::notifyEvent(const accessibility::AccessibleEventO } break; } + case accessibility::AccessibleEventId::SELECTION_CHANGED_REMOVE: + { + uno::Reference< accessibility::XAccessible > xNewValue; + aEvent.NewValue >>= xNewValue; + if (xNewValue.is()) + { + uno::Reference<accessibility::XAccessibleContext> xContext + = xNewValue->getAccessibleContext(); + + if (xContext.is()) + { + if (xContext->getAccessibleRole() == accessibility::AccessibleRole::SHAPE) + { + if (xContext->getAccessibleChildCount() > 0) + { + uno::Reference<accessibility::XAccessible> xAccChild + = xContext->getAccessibleChild(0); + uno::Reference<css::accessibility::XAccessibleText> xAccText( + xAccChild, uno::UNO_QUERY); + if (xAccText.is()) + { + // see SELECTION_CHANGED case + resetParagraphInfo(); + notifyFocusedParagraphChanged(true); + } + } + } + } + } + break; + } case accessibility::AccessibleEventId::CHILD: { uno::Reference< accessibility::XAccessible > xChild; diff --git a/svx/source/accessibility/ChildrenManagerImpl.cxx b/svx/source/accessibility/ChildrenManagerImpl.cxx index 2926087060d3..cc107bc4b0df 100644 --- a/svx/source/accessibility/ChildrenManagerImpl.cxx +++ b/svx/source/accessibility/ChildrenManagerImpl.cxx @@ -38,6 +38,7 @@ #include <com/sun/star/lang/IndexOutOfBoundsException.hpp> #include <com/sun/star/view/XSelectionSupplier.hpp> #include <com/sun/star/container/XChild.hpp> +#include <comphelper/lok.hxx> #include <comphelper/types.hxx> #include <o3tl/safeint.hxx> #include <rtl/ustring.hxx> @@ -315,8 +316,9 @@ void ChildrenManagerImpl::CreateListOfVisibleShapes ( aBoundingBox.SetBottom( aPos.Y + aSize.Height ); // Insert shape if it is visible, i.e. its bounding box overlaps - // the visible area. - if ( aBoundingBox.Overlaps(aVisibleArea) ) + // the visible area. In the LOK case we skip the overlap check + // since we could remove a shape that is visible on the client. + if ( aBoundingBox.Overlaps(aVisibleArea) || comphelper::LibreOfficeKit::isActive()) raDescriptorList.emplace_back(xShape); } } @@ -482,7 +484,7 @@ void ChildrenManagerImpl::AddShape (const Reference<drawing::XShape>& rxShape) if (xParent != mxShapeList) return; - if (!aBoundingBox.Overlaps(aVisibleArea)) + if (!aBoundingBox.Overlaps(aVisibleArea) && !comphelper::LibreOfficeKit::isActive()) return; // Add shape to list of visible shapes.