desktop/qa/desktop_lib/test_desktop_lib.cxx | 6 desktop/source/lib/init.cxx | 62 ++++ include/LibreOfficeKit/LibreOfficeKit.h | 6 include/LibreOfficeKit/LibreOfficeKit.hxx | 30 ++ include/LibreOfficeKit/LibreOfficeKitEnums.h | 41 +++ include/sfx2/viewsh.hxx | 4 libreofficekit/source/gtk/lokdocview.cxx | 3 sfx2/source/view/viewsh.cxx | 360 +++++++++++++++++++++++++-- 8 files changed, 492 insertions(+), 20 deletions(-)
New commits: commit 2cff51517ca0c609f7fa0aba6a798562895b6d86 Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Thu May 4 12:11:53 2023 +0200 Commit: Marco Cecchetti <marco.cecche...@collabora.com> CommitDate: Fri May 5 16:15:48 2023 +0200 lok: a11y: focused paragraph info sent to client For the currently focused paragraph the following data is notified to client: paragraph content, caret position, text selection start/end These data is kept as an instance state so the client can request such info at any time. Change-Id: Ic1a3be0d93472300b1b6a91fb0de5bad87c031aa Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151364 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Marco Cecchetti <marco.cecche...@collabora.com> diff --git a/desktop/qa/desktop_lib/test_desktop_lib.cxx b/desktop/qa/desktop_lib/test_desktop_lib.cxx index 55ef248ec932..d04c59aae76a 100644 --- a/desktop/qa/desktop_lib/test_desktop_lib.cxx +++ b/desktop/qa/desktop_lib/test_desktop_lib.cxx @@ -3642,9 +3642,13 @@ void DesktopLOKTest::testABI() offsetof(struct _LibreOfficeKitDocumentClass, setViewTimezone)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(69), offsetof(struct _LibreOfficeKitDocumentClass, setAccessibilityState)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(70), + offsetof(struct _LibreOfficeKitDocumentClass, getA11yFocusedParagraph)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(71), + offsetof(struct _LibreOfficeKitDocumentClass, getA11yCaretPosition)); // As above - CPPUNIT_ASSERT_EQUAL(documentClassOffset(70), sizeof(struct _LibreOfficeKitDocumentClass)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(72), sizeof(struct _LibreOfficeKitDocumentClass)); } CPPUNIT_TEST_SUITE_REGISTRATION(DesktopLOKTest); diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index b3350be8e8aa..62d8a93733e4 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -1312,6 +1312,10 @@ static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const cha static void doc_setViewTimezone(LibreOfficeKitDocument* pThis, int nId, const char* timezone); static void doc_setAccessibilityState(LibreOfficeKitDocument* pThis, int nId, bool bEnabled); + +static char* doc_getA11yFocusedParagraph(LibreOfficeKitDocument* pThis); + +static int doc_getA11yCaretPosition(LibreOfficeKitDocument* pThis); } // extern "C" namespace { @@ -1463,6 +1467,9 @@ LibLODocument_Impl::LibLODocument_Impl(uno::Reference <css::lang::XComponent> xC m_pDocumentClass->setAccessibilityState = doc_setAccessibilityState; + m_pDocumentClass->getA11yFocusedParagraph = doc_getA11yFocusedParagraph; + m_pDocumentClass->getA11yCaretPosition = doc_getA11yCaretPosition; + gDocumentClass = m_pDocumentClass; } pClass = m_pDocumentClass.get(); @@ -1769,6 +1776,9 @@ void CallbackFlushHandler::queue(const int type, CallbackData& aCallbackData) case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY: case LOK_CALLBACK_REFERENCE_MARKS: case LOK_CALLBACK_CELL_AUTO_FILL_AREA: + case LOK_CALLBACK_A11Y_FOCUS_CHANGED: + case LOK_CALLBACK_A11Y_CARET_CHANGED: + case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED: { const auto& pos = std::find(m_queue1.rbegin(), m_queue1.rend(), type); auto pos2 = toQueue2(pos); @@ -1827,6 +1837,9 @@ void CallbackFlushHandler::queue(const int type, CallbackData& aCallbackData) case LOK_CALLBACK_SET_PART: case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE: case LOK_CALLBACK_RULER_UPDATE: + case LOK_CALLBACK_A11Y_FOCUS_CHANGED: + case LOK_CALLBACK_A11Y_CARET_CHANGED: + case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED: { if (removeAll(type)) SAL_INFO("lok", "Removed dups of [" << type << "]: [" << aCallbackData.getPayload() << "]."); @@ -3776,6 +3789,46 @@ static char* doc_getPartPageRectangles(LibreOfficeKitDocument* pThis) return convertOUString(pDoc->getPartPageRectangles()); } +static char* doc_getA11yFocusedParagraph(LibreOfficeKitDocument* pThis) +{ + SolarMutexGuard aGuard; + + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg("Document doesn't support tiled rendering"); + return nullptr; + } + + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + { + return convertOUString(pViewShell->getA11yFocusedParagraph()); + + } + return nullptr; +} + +static int doc_getA11yCaretPosition(LibreOfficeKitDocument* pThis) +{ + SolarMutexGuard aGuard; + + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg("Document doesn't support tiled rendering"); + return -1; + } + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + { + return pViewShell->getA11yCaretPosition(); + + } + return -1; + +} + static char* doc_getPartName(LibreOfficeKitDocument* pThis, int nPart) { comphelper::ProfileZone aZone("doc_getPartName"); @@ -6401,12 +6454,9 @@ static void doc_setViewLanguage(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*p SolarMutexGuard aGuard; SetLastExceptionMsg(); - SAL_DEBUG("doc_setViewLanguage: nId: " << nId); OUString sLanguage = OStringToOUString(language, RTL_TEXTENCODING_UTF8); SfxLokHelper::setViewLanguage(nId, sLanguage); SfxLokHelper::setViewLocale(nId, sLanguage); - - SfxLokHelper::setAccessibilityState(nId, true); } unsigned char* doc_renderFont(LibreOfficeKitDocument* pThis, @@ -6950,8 +7000,12 @@ static void doc_setViewTimezone(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*p } } -static void doc_setAccessibilityState(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId, bool nEnabled) +static void doc_setAccessibilityState(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int nId, bool nEnabled) { + int nDocType = doc_getDocumentType(pThis); + if (nDocType != LOK_DOCTYPE_TEXT) + return; + SolarMutexGuard aGuard; if (gImpl) gImpl->maLastExceptionMsg.clear(); diff --git a/include/LibreOfficeKit/LibreOfficeKit.h b/include/LibreOfficeKit/LibreOfficeKit.h index 30089bb11e0d..ba51c5afeea9 100644 --- a/include/LibreOfficeKit/LibreOfficeKit.h +++ b/include/LibreOfficeKit/LibreOfficeKit.h @@ -496,6 +496,12 @@ struct _LibreOfficeKitDocumentClass /// @see lok::Document::setAccessibilityState(). void (*setAccessibilityState) (LibreOfficeKitDocument* pThis, int nId, bool nEnabled); + /// @see lok::Document::getA11yFocusedParagraph. + char* (*getA11yFocusedParagraph) (LibreOfficeKitDocument* pThis); + + /// @see lok::Document::getA11yCaretPosition. + int (*getA11yCaretPosition) (LibreOfficeKitDocument* pThis); + #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY }; diff --git a/include/LibreOfficeKit/LibreOfficeKit.hxx b/include/LibreOfficeKit/LibreOfficeKit.hxx index 859ab0d98555..f2951594f40c 100644 --- a/include/LibreOfficeKit/LibreOfficeKit.hxx +++ b/include/LibreOfficeKit/LibreOfficeKit.hxx @@ -858,6 +858,36 @@ public: mpDoc->pClass->setViewTimezone(mpDoc, nId, timezone); } + /** + * Enable/Disable accessibility support for the window with the specified nId. + * + * @param nId a view ID, returned by createView(). + * @param nEnabled true/false + */ + void setAccessibilityState(int nId, bool nEnabled) + { + mpDoc->pClass->setAccessibilityState(mpDoc, nId, nEnabled); + } + + /** + * Get the current focused paragraph info: + * { + * "content": paragraph content + * "start": selection start + * "end": selection end + * } + */ + char* getA11yFocusedParagraph() + { + return mpDoc->pClass->getA11yFocusedParagraph(mpDoc); + } + + /// Get the current text cursor position. + int getA11yCaretPosition() + { + return mpDoc->pClass->getA11yCaretPosition(mpDoc); + } + #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY }; diff --git a/include/LibreOfficeKit/LibreOfficeKitEnums.h b/include/LibreOfficeKit/LibreOfficeKitEnums.h index f57e00afa185..47b2d790738f 100644 --- a/include/LibreOfficeKit/LibreOfficeKitEnums.h +++ b/include/LibreOfficeKit/LibreOfficeKitEnums.h @@ -922,7 +922,40 @@ typedef enum * Informs the LibreOfficeKit client that the background color surrounding * the document has changed. */ - LOK_CALLBACK_APPLICATION_BACKGROUND_COLOR = 61 + LOK_CALLBACK_APPLICATION_BACKGROUND_COLOR = 61, + + /** + * Accessibility event: a paragraph get focus. + * The payload is a json with the following structure. + * + * { + * "content": "<paragraph text>" + * "position": N + * } + * where N is the position of the text cursor inside the focused paragraph. + */ + LOK_CALLBACK_A11Y_FOCUS_CHANGED = 62, + + /** + * Accessibility event: text cursor position has changed. + * + * { + * "position": N + * } + * where N is the position of the text cursor inside the focused paragraph. + */ + LOK_CALLBACK_A11Y_CARET_CHANGED = 63, + + /** + * Accessibility event: text cursor position has changed. + * + * { + * "start": N1 + * "end": N2 + * } + * where [N1,N2] is the range of the text selection inside the focused paragraph. + */ + LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED = 64 } LibreOfficeKitCallbackType; @@ -1075,6 +1108,12 @@ static inline const char* lokCallbackTypeToString(int nType) return "LOK_CALLBACK_VIEW_RENDER_STATE"; case LOK_CALLBACK_APPLICATION_BACKGROUND_COLOR: return "LOK_CALLBACK_APPLICATION_BACKGROUND_COLOR"; + case LOK_CALLBACK_A11Y_FOCUS_CHANGED: + return "LOK_CALLBACK_A11Y_FOCUS_CHANGED"; + case LOK_CALLBACK_A11Y_CARET_CHANGED: + return "LOK_CALLBACK_A11Y_CARET_CHANGED"; + case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED: + return "LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED"; } assert(!"Unknown LibreOfficeKitCallbackType type."); diff --git a/include/sfx2/viewsh.hxx b/include/sfx2/viewsh.hxx index 3b0bc9bdcaae..8356e0856f46 100644 --- a/include/sfx2/viewsh.hxx +++ b/include/sfx2/viewsh.hxx @@ -212,6 +212,7 @@ private: static void InitInterface_Impl(); LOKDocumentFocusListener& GetLOKDocumentFocusListener(); + const LOKDocumentFocusListener& GetLOKDocumentFocusListener() const; public: @@ -448,6 +449,9 @@ public: bool isBlockedCommand(OUString command); void SetStoringHelper(std::shared_ptr<SfxStoringHelper> xHelper) { m_xHelper = xHelper; } + + OUString getA11yFocusedParagraph() const; + int getA11yCaretPosition() const; }; diff --git a/libreofficekit/source/gtk/lokdocview.cxx b/libreofficekit/source/gtk/lokdocview.cxx index 76f94293956b..6d810f2d1e3f 100644 --- a/libreofficekit/source/gtk/lokdocview.cxx +++ b/libreofficekit/source/gtk/lokdocview.cxx @@ -1488,6 +1488,9 @@ callback (gpointer pData) case LOK_CALLBACK_EXPORT_FILE: case LOK_CALLBACK_VIEW_RENDER_STATE: case LOK_CALLBACK_APPLICATION_BACKGROUND_COLOR: + case LOK_CALLBACK_A11Y_FOCUS_CHANGED: + case LOK_CALLBACK_A11Y_CARET_CHANGED: + case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED: { // TODO: Implement me break; diff --git a/sfx2/source/view/viewsh.cxx b/sfx2/source/view/viewsh.cxx index 221e41080562..1dcc9758f0bd 100644 --- a/sfx2/source/view/viewsh.cxx +++ b/sfx2/source/view/viewsh.cxx @@ -19,6 +19,8 @@ #include <config_features.h> +#include <boost/property_tree/json_parser.hpp> + #include <sal/log.hxx> #include <svl/stritem.hxx> #include <svl/eitem.hxx> @@ -236,10 +238,20 @@ void SAL_CALL SfxClipboardChangeListener::changedContents( const datatransfer::c class LOKDocumentFocusListener : public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener > { - + const SfxViewShell* m_pViewShell; std::set< uno::Reference< uno::XInterface > > m_aRefList; + OUString m_sFocusedParagraph; + bool m_bFocusedParagraphNotified; + sal_Int32 m_nCaretPosition; + sal_Int32 m_nSelectionStart; + sal_Int32 m_nSelectionEnd; + OUString m_sSelectedText; + bool m_bIsEditingCell; + OUString m_sSelectedCellAddress; public: + LOKDocumentFocusListener(const SfxViewShell* pViewShell); + /// @throws lang::IndexOutOfBoundsException /// @throws uno::RuntimeException void attachRecursive( @@ -290,8 +302,97 @@ public: // XAccessibleEventListener virtual void SAL_CALL notifyEvent( const accessibility::AccessibleEventObject& aEvent ) override; + void notifyFocusedParagraphChanged(); + void notifyCaretChanged(); + void notifyTextSelectionChanged(); + + OUString getFocusedParagraph() const; + int getCaretPosition() const; }; +LOKDocumentFocusListener::LOKDocumentFocusListener(const SfxViewShell* pViewShell) + : m_pViewShell(pViewShell) + , m_bFocusedParagraphNotified(false) + , m_nCaretPosition(0) + , m_nSelectionStart(0) + , m_nSelectionEnd(0) + , m_bIsEditingCell(false) +{ +} + +OUString LOKDocumentFocusListener::getFocusedParagraph() const +{ + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::getFocusedParagraph: " << m_sFocusedParagraph); + const_cast<LOKDocumentFocusListener*>(this)->m_bFocusedParagraphNotified = true; + + sal_Int32 nSelectionStart = m_nSelectionStart; + sal_Int32 nSelectionEnd = m_nSelectionEnd; + if (nSelectionStart < 0 || nSelectionEnd < 0) + nSelectionStart = nSelectionEnd = m_nCaretPosition; + + boost::property_tree::ptree aPayloadTree; + aPayloadTree.put("content", m_sFocusedParagraph.toUtf8().getStr()); + aPayloadTree.put("start", nSelectionStart); + aPayloadTree.put("end", nSelectionEnd); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + std::string aPayload = aStream.str(); + OUString sRet = OUString::fromUtf8(aPayload); + return sRet; +} + +int LOKDocumentFocusListener::getCaretPosition() const +{ + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::getCaretPosition: " << m_nCaretPosition); + return m_nCaretPosition; +} + +void LOKDocumentFocusListener::notifyFocusedParagraphChanged() +{ + boost::property_tree::ptree aPayloadTree; + aPayloadTree.put("content", m_sFocusedParagraph.toUtf8().getStr()); + aPayloadTree.put("position", m_nCaretPosition); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + std::string aPayload = aStream.str(); + if (m_pViewShell) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyFocusedParagraphChanged: " << m_sFocusedParagraph); + m_bFocusedParagraphNotified = true; + m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_FOCUS_CHANGED, aPayload.c_str()); + } +} + +void LOKDocumentFocusListener::notifyCaretChanged() +{ + boost::property_tree::ptree aPayloadTree; + aPayloadTree.put("position", m_nCaretPosition); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + std::string aPayload = aStream.str(); + if (m_pViewShell) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyCaretChanged: " << m_nCaretPosition); + m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_CARET_CHANGED, aPayload.c_str()); + } +} + +void LOKDocumentFocusListener::notifyTextSelectionChanged() +{ + boost::property_tree::ptree aPayloadTree; + aPayloadTree.put("start", m_nSelectionStart); + aPayloadTree.put("end", m_nSelectionEnd); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + std::string aPayload = aStream.str(); + if (m_pViewShell) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyTextSelectionChanged: " + << "start: " << m_nSelectionStart << ", end: " << m_nSelectionEnd); + m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED, aPayload.c_str()); + } +} + void LOKDocumentFocusListener::disposing( const lang::EventObject& aEvent ) { @@ -302,32 +403,66 @@ void LOKDocumentFocusListener::disposing( const lang::EventObject& aEvent ) } +namespace +{ +bool hasState(const accessibility::AccessibleEventObject& aEvent, ::sal_Int64 nState) +{ + bool res = false; + uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY); + if (xContext.is()) + { + ::sal_Int64 nStateSet = xContext->getAccessibleStateSet(); + res = (nStateSet & nState) != 0; + } + return res; +} + +bool isFocused(const accessibility::AccessibleEventObject& aEvent) +{ + return hasState(aEvent, accessibility::AccessibleStateType::FOCUSED); +} +} // anonymous namespace + void LOKDocumentFocusListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent ) { - SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent:event id: " << aEvent.EventId); - try { + try + { switch( aEvent.EventId ) { case accessibility::AccessibleEventId::STATE_CHANGED: { - sal_Int16 nState = accessibility::AccessibleStateType::INVALID; + uno::Reference< accessibility::XAccessible > xAccStateChanged = getAccessible(aEvent); + sal_Int64 nState = accessibility::AccessibleStateType::INVALID; aEvent.NewValue >>= nState; - SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: STATE_CHANGED: XAccessible: " << getAccessible(aEvent).get()); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: " + << "STATE_CHANGED: XAccessible: " << xAccStateChanged.get() << ", nState: " << nState); if( accessibility::AccessibleStateType::FOCUSED == nState ) { SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: FOCUSED"); - uno::Reference<css::accessibility::XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY); + if (m_bIsEditingCell) + { + if (!hasState(aEvent, accessibility::AccessibleStateType::ACTIVE)) + { + SAL_WARN("lok.a11y", + "LOKDocumentFocusListener::notifyEvent: FOCUSED: Cell not ACTIVE for editing yet"); + return; + } + } + uno::Reference<css::accessibility::XAccessibleText> xAccText(xAccStateChanged, uno::UNO_QUERY); if( xAccText.is() ) { OUString sText = xAccText->getText(); sal_Int32 nLength = sText.getLength(); - SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: xAccText: " << xAccText.get() << ", text: " << sText); + sal_Int32 nCaretPosition = xAccText->getCaretPosition(); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: xAccText: " << xAccText.get() + << ", text: >" << sText << "<, caret pos: " << nCaretPosition); if (nLength) { - css::uno::Reference<css::accessibility::XAccessibleTextAttributes> xAccTextAttr(xAccText, uno::UNO_QUERY); + css::uno::Reference<css::accessibility::XAccessibleTextAttributes> + xAccTextAttr(xAccText, uno::UNO_QUERY); css::uno::Sequence< OUString > aRequestedAttributes; sal_Int32 nPos = 0; @@ -335,8 +470,10 @@ void LOKDocumentFocusListener::notifyEvent( const accessibility::AccessibleEvent { css::accessibility::TextSegment aTextSegment = xAccText->getTextAtIndex(nPos, css::accessibility::AccessibleTextType::ATTRIBUTE_RUN); - SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: text segment: '" << aTextSegment.SegmentText - << "', start: " << aTextSegment.SegmentStart << ", end: " << aTextSegment.SegmentEnd); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: " + << "text segment: '" << aTextSegment.SegmentText + << "', start: " << aTextSegment.SegmentStart + << ", end: " << aTextSegment.SegmentEnd); css::uno::Sequence< css::beans::PropertyValue > aRunAttributeList; if (xAccTextAttr.is()) @@ -349,7 +486,8 @@ void LOKDocumentFocusListener::notifyEvent( const accessibility::AccessibleEvent } sal_Int32 nSize = aRunAttributeList.getLength(); - SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: attribute list size: " << nSize); + SAL_INFO("lok.a11y", + "LOKDocumentFocusListener::notifyEvent: attribute list size: " << nSize); if (nSize) { OUString sValue; @@ -398,18 +536,172 @@ void LOKDocumentFocusListener::notifyEvent( const accessibility::AccessibleEvent } } sAttributes += " }"; - SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: attributes: " << sAttributes); + SAL_INFO("lok.a11y", + "LOKDocumentFocusListener::notifyEvent: attributes: " << sAttributes); } nPos = aTextSegment.SegmentEnd + 1; } } + if (!m_bFocusedParagraphNotified || m_sFocusedParagraph != sText) + { + m_sFocusedParagraph = sText; + m_nCaretPosition = nCaretPosition; + notifyFocusedParagraphChanged(); + } + } + } + + break; + } + + case accessibility::AccessibleEventId::CARET_CHANGED: + { + if (!isFocused(aEvent)) + { + SAL_WARN("lok.a11y", + "LOKDocumentFocusListener::notifyEvent: CARET_CHANGED: skip non focused paragraph"); + return; + } + + sal_Int32 nNewPos = -1; + aEvent.NewValue >>= nNewPos; + sal_Int32 nOldPos = -1; + aEvent.OldValue >>= nOldPos; + + if (nNewPos >= 0) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: CARET_CHANGED: " << + "new pos: " << nNewPos << ", nOldPos: " << nOldPos); + uno::Reference<css::accessibility::XAccessibleText> + xAccText(getAccessible(aEvent), uno::UNO_QUERY); + if( xAccText.is() ) + { + OUString sText = xAccText->getText(); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: CARET_CHANGED: " + << "xAccText: " << xAccText.get() << ", text: >" << sText << "<"); + m_nCaretPosition = nNewPos; + m_nSelectionStart = m_nSelectionEnd = m_nCaretPosition; + notifyCaretChanged(); } } break; } + case accessibility::AccessibleEventId::TEXT_CHANGED: + { + if (!isFocused(aEvent)) + { + SAL_WARN("lok.a11y", + "LOKDocumentFocusListener::notifyEvent: TEXT_CHANGED: skip non focused paragraph"); + return; + } + + accessibility::TextSegment aDeletedText; + accessibility::TextSegment aInsertedText; + + if (aEvent.OldValue >>= aDeletedText) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: TEXT_CHANGED: " + << "deleted text: >" << aDeletedText.SegmentText << "<"); + } + if (aEvent.NewValue >>= aInsertedText) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: TEXT_CHANGED: " + << "inserted text: >" << aInsertedText.SegmentText << "<"); + } + uno::Reference<css::accessibility::XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY); + if (xAccText.is()) + { + OUString sText = xAccText->getText(); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: TEXT_CHANGED: " + << "xAccText: " << xAccText.get() << ", text: >" << sText << "<"); + m_sFocusedParagraph = sText; + m_bFocusedParagraphNotified = false; + } + + break; + } + case accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED: + { + if (!isFocused(aEvent)) + { + SAL_WARN("lok.a11y", + "LOKDocumentFocusListener::notifyEvent: TEXT_SELECTION_CHANGED: skip non focused paragraph"); + return; + } + + uno::Reference<css::accessibility::XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY); + if (xAccText.is()) + { + OUString sText = xAccText->getText(); + sal_Int32 nSelectionStart = xAccText->getSelectionStart(); + sal_Int32 nSelectionEnd = xAccText->getSelectionEnd(); + m_sSelectedText = xAccText->getSelectedText(); + + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: TEXT_SELECTION_CHANGED: " + << "\n xAccText: " << xAccText.get() << ", text: >" << sText << "<" + << "\n start: " << nSelectionStart << ", end: " << nSelectionEnd + << "\n selected text: >" << m_sSelectedText << "<"); + + // This should not be risky since selection start/end are set also on CARET_CHANGED event + if (nSelectionStart == m_nSelectionStart && nSelectionEnd == m_nSelectionEnd) + return; + + // We send a message to client also when start/end are -1, in this way the client knows + // 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 = nSelectionStart; + m_nSelectionEnd = nSelectionEnd; + + // Calc: when editing a formula send the update content + if (m_bIsEditingCell && !m_sSelectedCellAddress.isEmpty() + && !m_sSelectedText.isEmpty() && sText.startsWith("=")) + { + notifyFocusedParagraphChanged(); + } + notifyTextSelectionChanged(); + } + + break; + } + case accessibility::AccessibleEventId::SELECTION_CHANGED: + { + uno::Reference< accessibility::XAccessible > xNewValue; + aEvent.NewValue >>= xNewValue; + if (xNewValue.is()) + { + uno::Reference< accessibility::XAccessibleContext > xContext = + xNewValue->getAccessibleContext(); + + if (xContext.is()) + { + OUString sName = xContext->getAccessibleName(); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: SELECTION_CHANGED: this: " << this + << ", selected cell address: >" << sName << "<" + << ", m_bIsEditingCell: " << m_bIsEditingCell); + if (m_bIsEditingCell && !sName.isEmpty()) + { + m_sSelectedCellAddress = sName; + // Check cell address: "$Sheet1.A10". + // On cell editing SELECTION_CHANGED is not emitted when selection is expanded. + // So selection can't be a cell range. + sal_Int32 nDotIndex = m_sSelectedText.indexOf('.'); + OUString sCellAddress = m_sSelectedText.copy(nDotIndex + 1); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: SELECTION_CHANGED: " + << "cell address: >" << sCellAddress << "<"); + if (m_sSelectedCellAddress == sCellAddress) + { + notifyFocusedParagraphChanged(); + notifyTextSelectionChanged(); + } + } + } + } + break; + } case accessibility::AccessibleEventId::CHILD: { uno::Reference< accessibility::XAccessible > xChild; @@ -487,6 +779,12 @@ void LOKDocumentFocusListener::attachRecursive( sal_Int64 nStateSet = xContext->getAccessibleStateSet(); + if (!m_bIsEditingCell) + { + ::rtl::OUString sName = xContext->getAccessibleName(); + m_bIsEditingCell = sName.startsWith("Cell"); + } + attachRecursive(xAccessible, xContext, nStateSet); } @@ -548,7 +846,24 @@ void LOKDocumentFocusListener::detachRecursive( { sal_Int64 nStateSet = xContext->getAccessibleStateSet(); - detachRecursive(xContext, nStateSet); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::detachRecursive(2): this: " << this + << ", name: " << xContext->getAccessibleName() + << ", parent: " << xContext->getAccessibleParent().get() + << ", child count: " << xContext->getAccessibleChildCount()); + + if (m_bIsEditingCell) + { + ::rtl::OUString sName = xContext->getAccessibleName(); + m_bIsEditingCell = !sName.startsWith("Cell"); + if (!m_bIsEditingCell) + { + m_sFocusedParagraph = ""; + m_nCaretPosition = 0; + notifyFocusedParagraphChanged(); + } + } + + detachRecursive(xContext, nStateSet); } void LOKDocumentFocusListener::detachRecursive( @@ -1481,6 +1796,18 @@ SfxViewShell::~SfxViewShell() pFrameWin->ReleaseLOKNotifier(); } +OUString SfxViewShell::getA11yFocusedParagraph() const +{ + const LOKDocumentFocusListener& rDocFocusListener = GetLOKDocumentFocusListener(); + return rDocFocusListener.getFocusedParagraph(); +} + +int SfxViewShell::getA11yCaretPosition() const +{ + const LOKDocumentFocusListener& rDocFocusListener = GetLOKDocumentFocusListener(); + return rDocFocusListener.getCaretPosition(); +} + bool SfxViewShell::PrepareClose ( bool bUI // TRUE: Allow Dialog and so on, FALSE: silent-mode @@ -2007,10 +2334,15 @@ LOKDocumentFocusListener& SfxViewShell::GetLOKDocumentFocusListener() if (mpLOKDocumentFocusListener) return *mpLOKDocumentFocusListener; - mpLOKDocumentFocusListener.reset(new LOKDocumentFocusListener); + mpLOKDocumentFocusListener.reset(new LOKDocumentFocusListener(this)); return *mpLOKDocumentFocusListener; } +const LOKDocumentFocusListener& SfxViewShell::GetLOKDocumentFocusListener() const +{ + return const_cast<SfxViewShell*>(this)->GetLOKDocumentFocusListener(); +} + void SfxViewShell::SetLOKAccessibilityState(bool bEnabled) { if (bEnabled == mbLOKAccessibilityEnabled)