desktop/source/lib/init.cxx | 2 sfx2/source/view/viewsh.cxx | 133 ++++++++++++++++++++--- svx/source/accessibility/ChildrenManagerImpl.cxx | 8 - 3 files changed, 125 insertions(+), 18 deletions(-)
New commits: commit b6807298c8dce696152c52447d8fe7759a6f683a Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Tue Oct 3 12:34:45 2023 +0200 Commit: Tomaž Vajngerl <qui...@gmail.com> CommitDate: Wed Oct 11 00:02:54 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> diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 3bd6fa10fe68..5b50b0d8181c 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -7182,7 +7182,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 8ad4fe841c88..7fcb8c1920d8 100644 --- a/sfx2/source/view/viewsh.cxx +++ b/sfx2/source/view/viewsh.cxx @@ -267,6 +267,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. @@ -360,6 +382,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; @@ -687,6 +719,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) @@ -887,6 +920,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) { @@ -916,6 +963,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); @@ -931,10 +987,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); @@ -1087,14 +1153,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 { @@ -1149,9 +1209,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() @@ -1180,8 +1238,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 << "<" + << ", 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; @@ -1202,6 +1276,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 36390c7a033b..95b24ab2d1cf 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 <o3tl/sorted_vector.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.