tdf#135586 sw a11y: Use BLOCK_QUOTE role for "Block Quotation" para

If a paragraph is using the "Block Quotation" paragraph style,
report the newly introduced BLOCK_QUOTE a11y role for it instead
of the PARAGRAPH role.

This is similar to how the HEADING role is reported when
one of the paragraph styles for headings is used
(s. `SwAccessibleParagraph::GetRealHeadingLevel`).

This is also in line with what is documented for mapping
LO elements to tagged PDF elements in
sw/inc/EnhancedPDFExportHelper.hxx:

> * Mapping of OOo elements to tagged pdf elements:
> *
> * OOo element                              tagged pdf element
> * -----------                              ------------------
> * [...]
> * SwFormat Quotations                       BlockQuote

This makes the Orca screen reader with the gtk3 VCL plugin on Linux
and the NVDA screen reader on Windows explicitly announce a paragraph
as block quote when moving there with the cursor.

Adapt some places that have specific handling for the
PARAGRAPH role to take into account the BLOCK_QUOTE
role as well.

Change-Id: I4a89625c2a3f07d37df09e68cb7045a59cfff633
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158574
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
diff --git a/offapi/com/sun/star/text/AccessibleParagraphView.idl b/offapi/com/sun/star/text/AccessibleParagraphView.idl
index 4b5a9d2..108faed 100644
--- a/offapi/com/sun/star/text/AccessibleParagraphView.idl
+++ b/offapi/com/sun/star/text/AccessibleParagraphView.idl
@@ -49,9 +49,10 @@ service AccessibleParagraphView
                children of the paragraph fragment but of the document view
                itself.</li>
            <li>The role is either
                ::com::sun::star::accessibility::AccessibleRole::PARAGRAPH or
                ::com::sun::star::accessibility::AccessibleRole::PARAGRAPH,
                ::com::sun::star::accessibility::AccessibleRole::BLOCK_QUOTE or
                ::com::sun::star::accessibility::AccessibleRole::HEADING.
                The later one is returned
                The latter is returned
                if the paragraph's style is contained in the chapter
                numbering of a text document.
            <li>The name is "paragraph" or "heading" (or the equivalent term
diff --git a/sw/source/core/access/accmap.cxx b/sw/source/core/access/accmap.cxx
index 9b3ac848..0df41e3 100644
--- a/sw/source/core/access/accmap.cxx
+++ b/sw/source/core/access/accmap.cxx
@@ -899,7 +899,8 @@ void SwAccessibleMap::FireEvent( const SwAccessibleEvent_Impl& rEvent )
        if( aIter != mpFrameMap->end() )
        {
            rtl::Reference < SwAccessibleContext > xContext( (*aIter).second.get() );
            if (xContext.is() && xContext->getAccessibleRole() == AccessibleRole::PARAGRAPH)
            if (xContext.is() && (xContext->getAccessibleRole() == AccessibleRole::PARAGRAPH
                                  || xContext->getAccessibleRole() == AccessibleRole::BLOCK_QUOTE))
            {
                xAccImpl = xContext.get();
            }
@@ -1333,7 +1334,8 @@ void SwAccessibleMap::InvalidateShapeInParaSelection()
                                if (xAccParent.is())
                                {
                                    uno::Reference< XAccessibleContext > xAccContext = xAccParent->getAccessibleContext();
                                    if(xAccContext.is() && xAccContext->getAccessibleRole() == AccessibleRole::PARAGRAPH)
                                    if(xAccContext.is() && (xAccContext->getAccessibleRole() == AccessibleRole::PARAGRAPH ||
                                                             xAccContext->getAccessibleRole() == AccessibleRole::BLOCK_QUOTE))
                                    {
                                        SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xAccContext.get());
                                        if(pAccFrame->IsSelectedInDoc())
@@ -1597,7 +1599,8 @@ void SwAccessibleMap::DoInvalidateShapeSelection(bool bInvalidateFocusMode /*=fa
                    if (xPara.is())
                    {
                        uno::Reference< XAccessibleContext > xParaContext = xPara->getAccessibleContext();
                        if (xParaContext.is() && xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH)
                        if (xParaContext.is() && (xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH
                                                  || xParaContext->getAccessibleRole() == AccessibleRole::BLOCK_QUOTE))
                        {
                            SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xPara.get());
                            if (pAccPara)
@@ -1617,7 +1620,8 @@ void SwAccessibleMap::DoInvalidateShapeSelection(bool bInvalidateFocusMode /*=fa
        {
            uno::Reference< XAccessible > xPara = pAccShape->getAccessibleParent();
            uno::Reference< XAccessibleContext > xParaContext = xPara->getAccessibleContext();
            if (xParaContext.is() && xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH)
            if (xParaContext.is() && (xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH
                                      || xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH))
            {
                SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xPara.get());
                if (m_setParaAdd.count(pAccPara) == 0 )
diff --git a/sw/source/core/access/accpara.cxx b/sw/source/core/access/accpara.cxx
index 96e58f3..7fb728f 100644
--- a/sw/source/core/access/accpara.cxx
+++ b/sw/source/core/access/accpara.cxx
@@ -281,18 +281,22 @@ void SwAccessibleParagraph::InvalidateContent_( bool bVisibleDataFired )
        FireVisibleDataEvent();
    }

    bool bNewIsBlockQuote = IsBlockQuote();
    bool bNewIsHeading = IsHeading();
    //Get the real heading level, Heading1 ~ Heading10
    m_nHeadingLevel = GetRealHeadingLevel();
    bool bOldIsBlockQuote;
    bool bOldIsHeading;
    {
        std::scoped_lock aGuard( m_Mutex );
        bOldIsBlockQuote = m_bIsBlockQuote;
        bOldIsHeading = m_bIsHeading;
        m_bIsBlockQuote = bNewIsBlockQuote;
        if( m_bIsHeading != bNewIsHeading )
            m_bIsHeading = bNewIsHeading;
    }

    if( bNewIsHeading != bOldIsHeading )
    if (bNewIsBlockQuote != bOldIsBlockQuote || bNewIsHeading != bOldIsHeading)
    {
        // The role has changed
        AccessibleEventObject aEvent;
@@ -397,6 +401,7 @@ SwAccessibleParagraph::SwAccessibleParagraph(
        const SwTextFrame& rTextFrame )
    : SwAccessibleContext( pInitMap, AccessibleRole::PARAGRAPH, &rTextFrame )
    , m_nOldCaretPos( -1 )
    , m_bIsBlockQuote(false)
    , m_bIsHeading( false )
    //Get the real heading level, Heading1 ~ Heading10
    , m_nHeadingLevel (-1)
@@ -405,6 +410,7 @@ SwAccessibleParagraph::SwAccessibleParagraph(
    , m_bLastHasSelection(false)  //To add TEXT_SELECTION_CHANGED event
{
    StartListening(const_cast<SwTextFrame&>(rTextFrame));
    m_bIsBlockQuote = IsBlockQuote();
    m_bIsHeading = IsHeading();
    //Get the real heading level, Heading1 ~ Heading10
    m_nHeadingLevel = GetRealHeadingLevel();
@@ -3504,16 +3510,13 @@ bool SwAccessibleParagraph::GetSelectionAtIndex(
sal_Int16 SAL_CALL SwAccessibleParagraph::getAccessibleRole()
{
    SolarMutexGuard g;

    //Get the real heading level, Heading1 ~ Heading10
    if (m_nHeadingLevel > 0)
    {
        return AccessibleRole::HEADING;
    }
    if (m_bIsBlockQuote)
        return AccessibleRole::BLOCK_QUOTE;
    else
    {
        return AccessibleRole::PARAGRAPH;
    }
}

//Get the real heading level, Heading1 ~ Heading10
@@ -3538,6 +3541,16 @@ sal_Int32 SwAccessibleParagraph::GetRealHeadingLevel()
    return -1;
}

bool SwAccessibleParagraph::IsBlockQuote()
{
    uno::Reference<css::beans::XPropertySet> xPortion = CreateUnoPortion(0, 0);
    uno::Any aStyleAny = xPortion->getPropertyValue("ParaStyleName");
    OUString sValue;
    if (aStyleAny >>= sValue)
        return sValue == "Quotations";
    return false;
}

uno::Any SAL_CALL SwAccessibleParagraph::getExtendedAttributes()
{
    SolarMutexGuard g;
diff --git a/sw/source/core/access/accpara.hxx b/sw/source/core/access/accpara.hxx
index 3f6c572..890604a 100644
--- a/sw/source/core/access/accpara.hxx
+++ b/sw/source/core/access/accpara.hxx
@@ -78,6 +78,7 @@ class SwAccessibleParagraph :
                            // as the cursor is inside this object (protected by
                            // mutex)

    bool m_bIsBlockQuote;
    bool m_bIsHeading;    // protected by base classes mutex
    sal_Int32 m_nHeadingLevel;

@@ -369,6 +370,7 @@ public:
    // XAccessibleExtendedAttributes
    virtual css::uno::Any SAL_CALL getExtendedAttributes() override ;
    sal_Int32 GetRealHeadingLevel();
    bool IsBlockQuote();

    // #i89175#
    // XAccessibleMultiLineText
diff --git a/sw/source/core/access/acctextframe.cxx b/sw/source/core/access/acctextframe.cxx
index 6b5340c..e1b5cd0 100644
--- a/sw/source/core/access/acctextframe.cxx
+++ b/sw/source/core/access/acctextframe.cxx
@@ -151,7 +151,8 @@ sal_Bool SAL_CALL SwAccessibleTextFrame::isAccessibleChildSelected( sal_Int64 nC

    if( xContext.is() )
    {
        if( xContext->getAccessibleRole() == AccessibleRole::PARAGRAPH )
        const sal_Int16 nRole = xContext->getAccessibleRole();
        if (nRole  == AccessibleRole::PARAGRAPH || nRole == AccessibleRole::BLOCK_QUOTE)
        {
            uno::Reference< css::accessibility::XAccessibleText >
                xText(xAcc, uno::UNO_QUERY);