summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarco Cecchetti <marco.cecchetti@collabora.com>2023-03-23 08:33:16 +0100
committerMiklos Vajna <vmiklos@collabora.com>2023-06-02 08:04:38 +0200
commit14b3ad0b4e5a540f436db3467f8a4051392f8479 (patch)
treee25797ee8638f1d5a0136cd2ee23ab4d9c6e67b2
parent8731701481de649a65d59e018d0ba0c381f670c3 (diff)
lok: accessibility event listener for focused paragraph
LOKDocumentFocusListener keeps track of the currently focused paragraph. Also includes: Author: Andras Timar <andras.timar@collabora.com> Date: Mon May 15 22:06:10 2023 +0200 fix unused exception parameter 'e' Change-Id: Ibfee099e4e9b724648d7500b9ebb4e8ab84989b8 and: Author: Marco Cecchetti <marco.cecchetti@collabora.com> Date: Thu May 4 12:11:53 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 and: Author: Marco Cecchetti <marco.cecchetti@collabora.com> Date: Sun May 7 11:52:14 2023 +0200 fixup! lok: accessibility event listener for focused paragraph It seems it was not a good idea using a unique_ptr as smart pointer for an instance of LOKDocumentFocusListener Change-Id: I8e6b0f48fee3c5db3c9b074a663f7f3fb96a601e Change-Id: I0fa400694f3129608228ade0b96e0b4e0aee87e2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152488 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
-rw-r--r--desktop/qa/desktop_lib/test_desktop_lib.cxx8
-rw-r--r--desktop/source/lib/init.cxx144
-rw-r--r--include/LibreOfficeKit/LibreOfficeKit.h9
-rw-r--r--include/LibreOfficeKit/LibreOfficeKit.hxx30
-rw-r--r--include/LibreOfficeKit/LibreOfficeKitEnums.h41
-rw-r--r--include/sal/log-areas.dox1
-rw-r--r--include/sfx2/lokhelper.hxx2
-rw-r--r--include/sfx2/viewsh.hxx11
-rw-r--r--libreofficekit/source/gtk/lokdocview.cxx3
-rw-r--r--sfx2/source/view/lokhelper.cxx17
-rw-r--r--sfx2/source/view/viewsh.cxx741
11 files changed, 968 insertions, 39 deletions
diff --git a/desktop/qa/desktop_lib/test_desktop_lib.cxx b/desktop/qa/desktop_lib/test_desktop_lib.cxx
index 675ebfe82e8d..74db478c09a3 100644
--- a/desktop/qa/desktop_lib/test_desktop_lib.cxx
+++ b/desktop/qa/desktop_lib/test_desktop_lib.cxx
@@ -3641,9 +3641,15 @@ void DesktopLOKTest::testABI()
CPPUNIT_ASSERT_EQUAL(documentClassOffset(67), offsetof(struct _LibreOfficeKitDocumentClass, getEditMode));
CPPUNIT_ASSERT_EQUAL(documentClassOffset(68),
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(69), 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 8561760febce..13922571d9ce 100644
--- a/desktop/source/lib/init.cxx
+++ b/desktop/source/lib/init.cxx
@@ -1309,6 +1309,11 @@ 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 {
@@ -1361,6 +1366,45 @@ vcl::Font FindFont_FallbackToDefault(std::u16string_view rFontName)
return OutputDevice::GetDefaultFont(DefaultFontType::SANS_UNICODE, LANGUAGE_NONE,
GetDefaultFontFlags::NONE);
}
+
+int getDocumentType (LibreOfficeKitDocument* pThis)
+{
+ SetLastExceptionMsg();
+
+ LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
+
+ try
+ {
+ uno::Reference<lang::XServiceInfo> xDocument(pDocument->mxComponent, uno::UNO_QUERY_THROW);
+
+ if (xDocument->supportsService("com.sun.star.sheet.SpreadsheetDocument"))
+ {
+ return LOK_DOCTYPE_SPREADSHEET;
+ }
+ else if (xDocument->supportsService("com.sun.star.presentation.PresentationDocument"))
+ {
+ return LOK_DOCTYPE_PRESENTATION;
+ }
+ else if (xDocument->supportsService("com.sun.star.drawing.DrawingDocument"))
+ {
+ return LOK_DOCTYPE_DRAWING;
+ }
+ else if (xDocument->supportsService("com.sun.star.text.TextDocument") || xDocument->supportsService("com.sun.star.text.WebDocument"))
+ {
+ return LOK_DOCTYPE_TEXT;
+ }
+ else
+ {
+ SetLastExceptionMsg("unknown document type");
+ }
+ }
+ catch (const uno::Exception& exception)
+ {
+ SetLastExceptionMsg("exception: " + exception.Message);
+ }
+ return LOK_DOCTYPE_OTHER;
+}
+
} // anonymous namespace
LibLODocument_Impl::LibLODocument_Impl(uno::Reference <css::lang::XComponent> xComponent, int nDocumentId)
@@ -1458,6 +1502,11 @@ LibLODocument_Impl::LibLODocument_Impl(uno::Reference <css::lang::XComponent> xC
m_pDocumentClass->setViewTimezone = doc_setViewTimezone;
+ m_pDocumentClass->setAccessibilityState = doc_setAccessibilityState;
+
+ m_pDocumentClass->getA11yFocusedParagraph = doc_getA11yFocusedParagraph;
+ m_pDocumentClass->getA11yCaretPosition = doc_getA11yCaretPosition;
+
gDocumentClass = m_pDocumentClass;
}
pClass = m_pDocumentClass.get();
@@ -1764,6 +1813,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);
@@ -1822,6 +1874,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() << "].");
@@ -3615,40 +3670,7 @@ static int doc_getDocumentType (LibreOfficeKitDocument* pThis)
comphelper::ProfileZone aZone("doc_getDocumentType");
SolarMutexGuard aGuard;
- SetLastExceptionMsg();
-
- LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
-
- try
- {
- uno::Reference<lang::XServiceInfo> xDocument(pDocument->mxComponent, uno::UNO_QUERY_THROW);
-
- if (xDocument->supportsService("com.sun.star.sheet.SpreadsheetDocument"))
- {
- return LOK_DOCTYPE_SPREADSHEET;
- }
- else if (xDocument->supportsService("com.sun.star.presentation.PresentationDocument"))
- {
- return LOK_DOCTYPE_PRESENTATION;
- }
- else if (xDocument->supportsService("com.sun.star.drawing.DrawingDocument"))
- {
- return LOK_DOCTYPE_DRAWING;
- }
- else if (xDocument->supportsService("com.sun.star.text.TextDocument") || xDocument->supportsService("com.sun.star.text.WebDocument"))
- {
- return LOK_DOCTYPE_TEXT;
- }
- else
- {
- SetLastExceptionMsg("unknown document type");
- }
- }
- catch (const uno::Exception& exception)
- {
- SetLastExceptionMsg("exception: " + exception.Message);
- }
- return LOK_DOCTYPE_OTHER;
+ return getDocumentType(pThis);
}
static int doc_getParts (LibreOfficeKitDocument* pThis)
@@ -3770,6 +3792,46 @@ static char* doc_getPartPageRectangles(LibreOfficeKitDocument* pThis)
return convertOUString(pDoc->getPartPageRectangles());
}
+static char* doc_getA11yFocusedParagraph(LibreOfficeKitDocument* pThis)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ 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;
+ SetLastExceptionMsg();
+
+ 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");
@@ -6395,8 +6457,6 @@ static void doc_setViewLanguage(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*p
SfxLokHelper::setViewLocale(nId, sLanguage);
}
-
-
unsigned char* doc_renderFont(LibreOfficeKitDocument* pThis,
const char* pFontName,
const char* pChar,
@@ -6938,6 +6998,18 @@ static void doc_setViewTimezone(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*p
}
}
+static void doc_setAccessibilityState(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int nId, bool nEnabled)
+{
+ SolarMutexGuard aGuard;
+ SetLastExceptionMsg();
+
+ int nDocType = getDocumentType(pThis);
+ if (nDocType != LOK_DOCTYPE_TEXT)
+ return;
+
+ SfxLokHelper::setAccessibilityState(nId, nEnabled);
+}
+
static char* lo_getError (LibreOfficeKit *pThis)
{
comphelper::ProfileZone aZone("lo_getError");
diff --git a/include/LibreOfficeKit/LibreOfficeKit.h b/include/LibreOfficeKit/LibreOfficeKit.h
index 767469666fef..ba51c5afeea9 100644
--- a/include/LibreOfficeKit/LibreOfficeKit.h
+++ b/include/LibreOfficeKit/LibreOfficeKit.h
@@ -493,6 +493,15 @@ struct _LibreOfficeKitDocumentClass
/// @see lok::Document::setViewTimezone().
void (*setViewTimezone) (LibreOfficeKitDocument* pThis, int nId, const char* timezone);
+ /// @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 2946d0baaf7d..65f263c368a6 100644
--- a/include/LibreOfficeKit/LibreOfficeKit.hxx
+++ b/include/LibreOfficeKit/LibreOfficeKit.hxx
@@ -859,6 +859,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/sal/log-areas.dox b/include/sal/log-areas.dox
index 51b005e1bb51..f209b6244f5f 100644
--- a/include/sal/log-areas.dox
+++ b/include/sal/log-areas.dox
@@ -325,6 +325,7 @@ certain functionality.
@li @c lok.fontsubst
@li @c lok.tiledrendering
@li @c lok.dialog
+@li @c lok.a11y - LOK accessibility
@section l10ntools
diff --git a/include/sfx2/lokhelper.hxx b/include/sfx2/lokhelper.hxx
index d975b4ade2bf..08d66fd538cc 100644
--- a/include/sfx2/lokhelper.hxx
+++ b/include/sfx2/lokhelper.hxx
@@ -83,6 +83,8 @@ public:
static void setViewLanguage(int nId, const OUString& rBcp47LanguageTag);
/// Set the default language for views.
static void setDefaultLanguage(const OUString& rBcp47LanguageTag);
+ /// Enable/Disable AT support for the given view.
+ static void setAccessibilityState(int nId, bool nEnabled);
/// Get the language used by the loading view (used for all save operations).
static const LanguageTag & getLoadLanguage();
/// Set the language used by the loading view (used for all save operations).
diff --git a/include/sfx2/viewsh.hxx b/include/sfx2/viewsh.hxx
index a9713eb2375f..b805f1cf99d4 100644
--- a/include/sfx2/viewsh.hxx
+++ b/include/sfx2/viewsh.hxx
@@ -57,6 +57,7 @@ class SfxPrinter;
class NotifyEvent;
class SfxInPlaceClient;
class SfxLokCallbackInterface;
+class LOKDocumentFocusListener;
class SfxStoringHelper;
class VCLXPopupMenu;
namespace rtl { class OStringBuffer; }
@@ -173,6 +174,8 @@ friend class SfxPrinterController;
LanguageTag maLOKLanguageTag;
LanguageTag maLOKLocale;
LOKDeviceFormFactor maLOKDeviceFormFactor;
+ bool mbLOKAccessibilityEnabled;
+ rtl::Reference<LOKDocumentFocusListener> mpLOKDocumentFocusListener;
std::unordered_set<OUString> mvLOKBlockedCommandList;
OUString maLOKTimezone;
bool maLOKIsTimezoneSet;
@@ -215,6 +218,9 @@ private:
/// SfxInterface initializer.
static void InitInterface_Impl();
+ LOKDocumentFocusListener& GetLOKDocumentFocusListener();
+ const LOKDocumentFocusListener& GetLOKDocumentFocusListener() const;
+
public:
SfxViewShell( SfxViewFrame& rFrame, SfxViewShellFlags nFlags );
@@ -427,6 +433,8 @@ public:
void SetLOKLanguageTag(const OUString& rBcp47LanguageTag);
/// Get the LibreOfficeKit language of this view.
const LanguageTag& GetLOKLanguageTag() const { return maLOKLanguageTag; }
+ /// Enable/Disable LibreOfficeKit AT support for this view.
+ void SetLOKAccessibilityState(bool bEnabled);
/// Get the LibreOfficeKit timezone of this view. See @SetLOKTimezone.
std::pair<bool, OUString> GetLOKTimezone() const
@@ -466,6 +474,9 @@ public:
StylesHighlighterColorMap& GetStylesHighlighterParaColorMap() { return ParaStylesColorMap; }
StylesHighlighterColorMap& GetStylesHighlighterCharColorMap() { return CharStylesColorMap; }
+
+ OUString getA11yFocusedParagraph() const;
+ int getA11yCaretPosition() const;
};
#endif // INCLUDED_SFX2_VIEWSH_HXX
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/lokhelper.cxx b/sfx2/source/view/lokhelper.cxx
index ad78f01e7398..356925a6a341 100644
--- a/sfx2/source/view/lokhelper.cxx
+++ b/sfx2/source/view/lokhelper.cxx
@@ -145,10 +145,11 @@ void SfxLokHelper::destroyView(int nId)
const ViewShellId nViewShellId(nId);
std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl();
- for (const SfxViewShell* pViewShell : rViewArr)
+ for (SfxViewShell* pViewShell : rViewArr)
{
if (pViewShell->GetViewShellId() == nViewShellId)
{
+ pViewShell->SetLOKAccessibilityState(false);
SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
SfxRequest aRequest(rViewFrame, SID_CLOSEWIN);
rViewFrame.Exec_Impl(aRequest);
@@ -312,6 +313,20 @@ void SfxLokHelper::setViewLanguage(int nId, const OUString& rBcp47LanguageTag)
}
}
+void SfxLokHelper::setAccessibilityState(int nId, bool nEnabled)
+{
+ std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
+
+ for (SfxViewShell* pViewShell : rViewArr)
+ {
+ if (pViewShell->GetViewShellId() == ViewShellId(nId))
+ {
+ pViewShell->SetLOKAccessibilityState(nEnabled);
+ return;
+ }
+ }
+}
+
void SfxLokHelper::setViewLocale(int nId, const OUString& rBcp47LanguageTag)
{
std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
diff --git a/sfx2/source/view/viewsh.cxx b/sfx2/source/view/viewsh.cxx
index 4e414d9c65bf..a818a98380bb 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>
@@ -46,9 +48,22 @@
#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
#include <com/sun/star/view/XRenderable.hpp>
#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
#include <cppuhelper/implbase.hxx>
#include <com/sun/star/ui/XAcceleratorConfiguration.hpp>
+#include <cppuhelper/weakref.hxx>
+
+#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/awt/FontSlant.hpp>
+
#include <comphelper/diagnose_ex.hxx>
#include <tools/urlobj.hxx>
#include <unotools/tempfile.hxx>
@@ -221,6 +236,664 @@ void SAL_CALL SfxClipboardChangeListener::changedContents( const datatransfer::c
delete pInfo;
}
+class LOKDocumentFocusListener :
+ public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener >
+{
+ static constexpr sal_Int64 MAX_ATTACHABLE_CHILDREN = 30;
+
+ 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(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const sal_Int64 nStateSet
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const sal_Int64 nStateSet
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ static uno::Reference< accessibility::XAccessible > getAccessible(const lang::EventObject& aEvent );
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const lang::EventObject& Source ) override;
+
+ // 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;
+ const char* pPayload = aPayload.c_str();
+ m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_FOCUS_CHANGED, pPayload);
+ }
+}
+
+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);
+ const char* pPayload = aPayload.c_str();
+ m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_CARET_CHANGED, pPayload);
+ }
+}
+
+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);
+ const char* pPayload = aPayload.c_str();
+ m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED, pPayload);
+ }
+}
+
+void LOKDocumentFocusListener::disposing( const lang::EventObject& aEvent )
+{
+ // Unref the object here, but do not remove as listener since the object
+ // might no longer be in a state that safely allows this.
+ if( aEvent.Source.is() )
+ m_aRefList.erase(aEvent.Source);
+
+}
+
+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 )
+{
+ try
+ {
+ switch( aEvent.EventId )
+ {
+ case accessibility::AccessibleEventId::STATE_CHANGED:
+ {
+ 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: " << xAccStateChanged.get() << ", nState: " << nState);
+
+ if( accessibility::AccessibleStateType::FOCUSED == nState )
+ {
+ SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: FOCUSED");
+
+ 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_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::Sequence< OUString > aRequestedAttributes;
+
+ sal_Int32 nPos = 0;
+ while (nPos < nLength)
+ {
+ 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);
+
+ css::uno::Sequence< css::beans::PropertyValue > aRunAttributeList;
+ if (xAccTextAttr.is())
+ {
+ aRunAttributeList = xAccTextAttr->getRunAttributes(nPos, aRequestedAttributes);
+ }
+ else
+ {
+ aRunAttributeList = xAccText->getCharacterAttributes(nPos, aRequestedAttributes);
+ }
+
+ sal_Int32 nSize = aRunAttributeList.getLength();
+ SAL_INFO("lok.a11y",
+ "LOKDocumentFocusListener::notifyEvent: attribute list size: " << nSize);
+ if (nSize)
+ {
+ OUString sValue;
+ OUString sAttributes = "{ ";
+ for (const auto& attribute: aRunAttributeList)
+ {
+ if (attribute.Name.isEmpty())
+ continue;
+
+ if (attribute.Name == "CharHeight" || attribute.Name == "CharWeight")
+ {
+ float fValue;
+ attribute.Value >>= fValue;
+ sValue = OUString::number(fValue);
+ }
+ else if (attribute.Name == "CharPosture")
+ {
+ awt::FontSlant nValue;
+ attribute.Value >>= nValue;
+ sValue = OUString::number(static_cast<unsigned int>(nValue));
+ }
+ else if (attribute.Name == "CharUnderline")
+ {
+ sal_Int16 nValue;
+ attribute.Value >>= nValue;
+ sValue = OUString::number(nValue);
+ }
+ else if (attribute.Name == "CharFontName")
+ {
+ attribute.Value >>= sValue;
+ }
+ else if (attribute.Name == "Rsid")
+ {
+ sal_uInt32 nValue;
+ attribute.Value >>= nValue;
+ sValue = OUString::number(nValue);
+ }
+
+ if (!sValue.isEmpty())
+ {
+ if (sAttributes != "{ ")
+ sAttributes += ", ";
+ sAttributes += attribute.Name + ": " + sValue;
+ sValue = "";
+ }
+ }
+ 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;
+ if( (aEvent.OldValue >>= xChild) && xChild.is() )
+ detachRecursive(xChild);
+
+ if( (aEvent.NewValue >>= xChild) && xChild.is() )
+ attachRecursive(xChild);
+
+ break;
+ }
+
+ case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ SAL_INFO("lok.a11y", "Invalidate all children called");
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch( const lang::IndexOutOfBoundsException& )
+ {
+ SAL_WARN("lok.a11y", "Focused object has invalid index in parent");
+ }
+}
+
+uno::Reference< accessibility::XAccessible > LOKDocumentFocusListener::getAccessible(const lang::EventObject& aEvent )
+{
+ uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY);
+
+ if( xAccessible.is() )
+ return xAccessible;
+
+ uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY);
+
+ if( xContext.is() )
+ {
+ uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
+ if( xParent.is() )
+ {
+ uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
+ if( xParentContext.is() )
+ {
+ return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
+ }
+ }
+ }
+
+ return uno::Reference< accessibility::XAccessible >();
+}
+
+void LOKDocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+)
+{
+ SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(1): xAccessible: " << xAccessible.get());
+
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ attachRecursive(xAccessible, xContext);
+}
+
+void LOKDocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+)
+{
+ SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(2): xAccessible: " << xAccessible.get()
+ << ", role: " << xContext->getAccessibleRole()
+ << ", name: " << xContext->getAccessibleName()
+ << ", parent: " << xContext->getAccessibleParent().get()
+ << ", child count: " << xContext->getAccessibleChildCount());
+
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ if (!m_bIsEditingCell)
+ {
+ ::rtl::OUString sName = xContext->getAccessibleName();
+ m_bIsEditingCell = sName.startsWith("Cell");
+ }
+
+ attachRecursive(xAccessible, xContext, nStateSet);
+}
+
+void LOKDocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const sal_Int64 nStateSet
+)
+{
+ SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(3) #1: this: " << this
+ << ", xAccessible: " << xAccessible.get()
+ << ", role: " << xContext->getAccessibleRole()
+ << ", name: " << xContext->getAccessibleName()
+ << ", parent: " << xContext->getAccessibleParent().get()
+ << ", child count: " << xContext->getAccessibleChildCount());
+
+ uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
+
+ if (!xBroadcaster.is())
+ return;
+ SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(3) #2: xBroadcaster.is()");
+ // If not already done, add the broadcaster to the list and attach as listener.
+ const uno::Reference< uno::XInterface >& xInterface = xBroadcaster;
+ if( m_aRefList.insert(xInterface).second )
+ {
+ SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(3) #3: m_aRefList.insert(xInterface).second");
+ xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
+
+
+ if( !(nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
+ {
+ sal_Int64 nmax = xContext->getAccessibleChildCount();
+ if( nmax > MAX_ATTACHABLE_CHILDREN )
+ nmax = MAX_ATTACHABLE_CHILDREN;
+
+ for( sal_Int64 n = 0; n < nmax; n++ )
+ {
+ uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ attachRecursive(xChild);
+ }
+ }
+ }
+}
+
+void LOKDocumentFocusListener::detachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+)
+{
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ detachRecursive(xContext);
+}
+
+void LOKDocumentFocusListener::detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+)
+{
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ 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(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const sal_Int64 nStateSet
+)
+{
+ uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
+
+ if( xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster) )
+ {
+ xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
+
+ if( !( nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
+ {
+ sal_Int64 nmax = xContext->getAccessibleChildCount();
+ if( nmax > MAX_ATTACHABLE_CHILDREN )
+ nmax = MAX_ATTACHABLE_CHILDREN;
+
+ for( sal_Int64 n = 0; n < nmax; n++ )
+ {
+ uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ detachRecursive(xChild);
+ }
+ }
+ }
+}
+
sal_uInt32 SfxViewShell_Impl::m_nLastViewShellId = 0;
SfxViewShell_Impl::SfxViewShell_Impl(SfxViewShellFlags const nFlags, ViewShellDocId nDocId)
@@ -1074,6 +1747,7 @@ SfxViewShell::SfxViewShell
, maLOKLanguageTag(LANGUAGE_NONE)
, maLOKLocale(LANGUAGE_NONE)
, maLOKDeviceFormFactor(LOKDeviceFormFactor::UNKNOWN)
+, mbLOKAccessibilityEnabled(false)
{
SetMargin( rViewFrame.GetMargin_Impl() );
@@ -1126,6 +1800,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
@@ -1645,6 +2331,61 @@ void SfxViewShell::SetLOKLanguageTag(const OUString& rBcp47LanguageTag)
maLOKLanguageTag = aFallbackTag;
}
+LOKDocumentFocusListener& SfxViewShell::GetLOKDocumentFocusListener()
+{
+ if (mpLOKDocumentFocusListener)
+ return *mpLOKDocumentFocusListener;
+
+ mpLOKDocumentFocusListener = new LOKDocumentFocusListener(this);
+ return *mpLOKDocumentFocusListener;
+}
+
+const LOKDocumentFocusListener& SfxViewShell::GetLOKDocumentFocusListener() const
+{
+ return const_cast<SfxViewShell*>(this)->GetLOKDocumentFocusListener();
+}
+
+void SfxViewShell::SetLOKAccessibilityState(bool bEnabled)
+{
+ if (bEnabled == mbLOKAccessibilityEnabled)
+ return;
+ mbLOKAccessibilityEnabled = bEnabled;
+
+ LOKDocumentFocusListener& rDocumentFocusListener = GetLOKDocumentFocusListener();
+
+ if (!pWindow)
+ return;
+
+ uno::Reference< accessibility::XAccessible > xAccessible =
+ pWindow->GetAccessible();
+
+ if (!xAccessible.is())
+ return;
+
+ if (mbLOKAccessibilityEnabled)
+ {
+ try
+ {
+ rDocumentFocusListener.attachRecursive(xAccessible);
+ }
+ catch (const uno::Exception&)
+ {
+ SAL_WARN("lok.a11y", "Exception caught processing LOKDocumentFocusListener::attachRecursive");
+ }
+ }
+ else
+ {
+ try
+ {
+ rDocumentFocusListener.detachRecursive(xAccessible);
+ }
+ catch (const uno::Exception&)
+ {
+ SAL_WARN("lok.a11y", "Exception caught processing LOKDocumentFocusListener::detachRecursive");
+ }
+ }
+}
+
void SfxViewShell::SetLOKLocale(const OUString& rBcp47LanguageTag)
{
maLOKLocale = LanguageTag(rBcp47LanguageTag, true).makeFallback();