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)

Reply via email to