diff options
-rw-r--r-- | desktop/source/lib/init.cxx | 15 | ||||
-rw-r--r-- | include/LibreOfficeKit/LibreOfficeKit.h | 2 | ||||
-rw-r--r-- | include/sfx2/lokhelper.hxx | 2 | ||||
-rw-r--r-- | include/sfx2/viewsh.hxx | 7 | ||||
-rw-r--r-- | sfx2/source/view/lokhelper.cxx | 14 | ||||
-rw-r--r-- | sfx2/source/view/viewsh.cxx | 428 |
6 files changed, 468 insertions, 0 deletions
diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 452ae0b60a64..1783a05b9484 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -1246,6 +1246,7 @@ static int doc_getView(LibreOfficeKitDocument* pThis); static int doc_getViewsCount(LibreOfficeKitDocument* pThis); static bool doc_getViewIds(LibreOfficeKitDocument* pThis, int* pArray, size_t nSize); static void doc_setViewLanguage(LibreOfficeKitDocument* pThis, int nId, const char* language); +static void doc_setAccessibilityState(LibreOfficeKitDocument* pThis, int nId, bool bEnabled); static unsigned char* doc_renderFontOrientation(LibreOfficeKitDocument* pThis, const char *pFontName, const char *pChar, @@ -1462,6 +1463,8 @@ LibLODocument_Impl::LibLODocument_Impl(uno::Reference <css::lang::XComponent> xC m_pDocumentClass->setViewTimezone = doc_setViewTimezone; + m_pDocumentClass->setAccessibilityState = doc_setAccessibilityState; + gDocumentClass = m_pDocumentClass; } pClass = m_pDocumentClass.get(); @@ -6426,9 +6429,21 @@ 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); +} + +static void doc_setAccessibilityState(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId, bool nEnabled) +{ + SolarMutexGuard aGuard; + if (gImpl) + gImpl->maLastExceptionMsg.clear(); + + SfxLokHelper::setAccessibilityState(nId, nEnabled); } diff --git a/include/LibreOfficeKit/LibreOfficeKit.h b/include/LibreOfficeKit/LibreOfficeKit.h index 4de2380998b4..dbc9cd7d7441 100644 --- a/include/LibreOfficeKit/LibreOfficeKit.h +++ b/include/LibreOfficeKit/LibreOfficeKit.h @@ -505,6 +505,8 @@ struct _LibreOfficeKitDocumentClass unsigned char* pBuffer, int x, int y); + /// @see lok::Document::setAccessibilityState(). + void (*setAccessibilityState) (LibreOfficeKitDocument* pThis, int nId, bool nEnabled); #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY }; diff --git a/include/sfx2/lokhelper.hxx b/include/sfx2/lokhelper.hxx index 90ddd52d01a4..ed0dad488035 100644 --- a/include/sfx2/lokhelper.hxx +++ b/include/sfx2/lokhelper.hxx @@ -80,6 +80,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 c34e1a69640b..3b0bc9bdcaae 100644 --- a/include/sfx2/viewsh.hxx +++ b/include/sfx2/viewsh.hxx @@ -56,6 +56,7 @@ class SfxPrinter; class NotifyEvent; class SfxInPlaceClient; class SfxLokCallbackInterface; +class LOKDocumentFocusListener; class SfxStoringHelper; namespace rtl { class OStringBuffer; } namespace vcl { class PrinterController; } @@ -169,6 +170,8 @@ friend class SfxPrinterController; LanguageTag maLOKLanguageTag; LanguageTag maLOKLocale; LOKDeviceFormFactor maLOKDeviceFormFactor; + bool mbLOKAccessibilityEnabled; + std::unique_ptr<LOKDocumentFocusListener> mpLOKDocumentFocusListener; std::unordered_set<OUString> mvLOKBlockedCommandList; OUString maLOKTimezone; bool maLOKIsTimezoneSet; @@ -208,6 +211,8 @@ private: /// SfxInterface initializer. static void InitInterface_Impl(); + LOKDocumentFocusListener& GetLOKDocumentFocusListener(); + public: SfxViewShell( SfxViewFrame *pFrame, SfxViewShellFlags nFlags ); @@ -405,6 +410,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 diff --git a/sfx2/source/view/lokhelper.cxx b/sfx2/source/view/lokhelper.cxx index 29f64adbb74b..f46ff512254d 100644 --- a/sfx2/source/view/lokhelper.cxx +++ b/sfx2/source/view/lokhelper.cxx @@ -310,6 +310,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 62d449415e6e..ac0ee93228dc 100644 --- a/sfx2/source/view/viewsh.cxx +++ b/sfx2/source/view/viewsh.cxx @@ -46,7 +46,20 @@ #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 <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> @@ -218,6 +231,368 @@ void SAL_CALL SfxClipboardChangeListener::changedContents( const datatransfer::c delete pInfo; } +class LOKDocumentFocusListener : + public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener > +{ + + std::set< uno::Reference< uno::XInterface > > m_aRefList; + +public: + /// @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 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); + +} + +void LOKDocumentFocusListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent ) +{ + SAL_DEBUG("LOKDocumentFocusListener::notifyEvent:event id: " << aEvent.EventId); + try { + switch( aEvent.EventId ) + { + case accessibility::AccessibleEventId::STATE_CHANGED: + { + sal_Int16 nState = accessibility::AccessibleStateType::INVALID; + aEvent.NewValue >>= nState; + SAL_DEBUG("LOKDocumentFocusListener::notifyEvent: STATE_CHANGED: XAccessible: " << getAccessible(aEvent).get()); + + if( accessibility::AccessibleStateType::FOCUSED == nState ) + { + SAL_DEBUG("LOKDocumentFocusListener::notifyEvent: FOCUSED"); + + uno::Reference<css::accessibility::XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY); + if( xAccText.is() ) + { + OUString sText = xAccText->getText(); + sal_Int32 nLength = sText.getLength(); + SAL_DEBUG("LOKDocumentFocusListener::notifyEvent: xAccText: " << xAccText.get() << ", text: " << sText); + + 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_DEBUG("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_DEBUG("LOKDocumentFocusListener::notifyEvent: attribute list size: " << nSize); + if (nSize) + { + OUString sValue; + OUString sAttributes = "{ "; + for (const auto& attribute: aRunAttributeList) + { + if (attribute.Name.isEmpty()) + continue; + + // OUString sType = attribute.Value.getValueTypeName(); + // //attribute.Value >>= sValue; + // if (sAttributes != "{ ") + // sAttributes += ", "; + // sAttributes += attribute.Name + ": " + sType; + + 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((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") + { + unsigned int nValue; + attribute.Value >>= nValue; + sValue = OUString::number(nValue); + } + + if (!sValue.isEmpty()) + { + if (sAttributes != "{ ") + sAttributes += ", "; + sAttributes += attribute.Name + ": "; + sAttributes += sValue; + sValue = ""; + } + } + sAttributes += " }"; + SAL_DEBUG("LOKDocumentFocusListener::notifyEvent: attributes: " << sAttributes); + SAL_DEBUG("------------------------------------------"); + } + nPos = aTextSegment.SegmentEnd + 1; + } + } + + } + } + + 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& e ) + { + 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_DEBUG("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_DEBUG("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(); + + attachRecursive(xAccessible, xContext, nStateSet); +} + +void LOKDocumentFocusListener::attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible, + const uno::Reference< accessibility::XAccessibleContext >& xContext, + const sal_Int64 nStateSet +) +{ + SAL_DEBUG("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 = + uno::Reference< accessibility::XAccessibleEventBroadcaster >(xContext, uno::UNO_QUERY); + + if (!xBroadcaster.is()) + return; + SAL_DEBUG("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_DEBUG("LOKDocumentFocusListener::attachRecursive(3) #3: m_aRefList.insert(xInterface).second"); + xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this)); + + const sal_Int32 MAX_ATTACHABLE_CHILDREN = 10; + sal_Int32 n, nmax = xContext->getAccessibleChildCount(); + if( ( nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) && nmax > MAX_ATTACHABLE_CHILDREN ) + nmax = MAX_ATTACHABLE_CHILDREN; + + for( n = 0; n < nmax; n++ ) + { + uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) ); + + if( xChild.is() ) + attachRecursive(xChild); + } + +// if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) ) +// { +// SAL_DEBUG("LOKDocumentFocusListener::attachRecursive(3) #4: !MANAGES_DESCENDANTS"); +// sal_Int32 n, nmax = xContext->getAccessibleChildCount(); +// for( 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(); + + detachRecursive(xContext, nStateSet); +} + +void LOKDocumentFocusListener::detachRecursive( + const uno::Reference< accessibility::XAccessibleContext >& xContext, + const sal_Int64 nStateSet +) +{ + uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster = + uno::Reference< accessibility::XAccessibleEventBroadcaster >(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 ) == 0 ) + { + sal_Int32 n, nmax = xContext->getAccessibleChildCount(); + for( 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) @@ -1067,6 +1442,8 @@ SfxViewShell::SfxViewShell , maLOKLanguageTag(LANGUAGE_NONE) , maLOKLocale(LANGUAGE_NONE) , maLOKDeviceFormFactor(LOKDeviceFormFactor::UNKNOWN) +, mbLOKAccessibilityEnabled(false) +, mpLOKDocumentFocusListener(nullptr) { SetMargin( pViewFrame->GetMargin_Impl() ); @@ -1602,6 +1979,57 @@ void SfxViewShell::SetLOKLanguageTag(const OUString& rBcp47LanguageTag) maLOKLanguageTag = aFallbackTag; } +LOKDocumentFocusListener& SfxViewShell::GetLOKDocumentFocusListener() +{ + if (mpLOKDocumentFocusListener) + return *mpLOKDocumentFocusListener; + + mpLOKDocumentFocusListener.reset(new LOKDocumentFocusListener); + return *mpLOKDocumentFocusListener; +} + +void SfxViewShell::SetLOKAccessibilityState(bool bEnabled) +{ + if (bEnabled == mbLOKAccessibilityEnabled) + return; + mbLOKAccessibilityEnabled = bEnabled; + + SAL_DEBUG("SfxViewShell::SetLOKAccessibilityState: bEnabled: " << 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("SetLOKAccessibilityState", "Exception caught processing LOKDocumentFocusListener::attachRecursive"); + } + } + else + { + try + { + rDocumentFocusListener.detachRecursive(xAccessible); + } + catch (const uno::Exception&) + { + SAL_WARN("SetLOKAccessibilityState", "Exception caught processing LOKDocumentFocusListener::detachRecursive"); + } + } +} + void SfxViewShell::SetLOKLocale(const OUString& rBcp47LanguageTag) { maLOKLocale = LanguageTag(rBcp47LanguageTag, true).makeFallback(); |