Files
loongoffice/editeng/source/accessibility/AccessibleStaticTextBase.cxx
Michael Weghorn 206543c7be [API CHANGE] tdf#150683 a11y: Switch a11y child index to 64 bit
With 16k column support in Calc enabled by default in

    commit 4c5f8ccf0a2320432b8fe91add1dcadf54d9fd58
    Date:   Tue Mar 8 12:44:49 2022 +0100

        change default Calc number of columns to 16384 (tdf#50916)

, the number of Calc cells in a spreadsheet is larger than
SAL_MAX_INT32, meaning that a 32-bit a11y child index is no more
enough and using it resulted in integer overflows in
methods handling corresponding Calc cells in the a11y layer.
This e.g. had the effect of the Orca and NVDA screen readers
not announcing focused or selected cells properly when their
a11y child index was out of the 32-bit integer range.

Switch the internal a11y child indices to 64 bit to
be able to handle this properly internally.

Since the platform APIs (at least AT-SPI on Linux and
IAccessible2 on Windows; from what I can see LO's macOS
a11y bridge doesn't directly expose the child index)
are still restricted to 32 bit, larger child indices
still cannot be exposed via the platform APIs.

As a consequence, use of the the IAccessible2 and
AT-SPI methods that use the child index remains
problematic in those cases where the child index
is larger. However, as an alternative to using the
AT-SPI Table interface and the IAccessibleTable/
IAccessibleTable2 interfaces with the child index
to retrieve information about a specific cell,
both AT-SPI and IAccessible2 also provide interfaces
to retrieve that information directly
from the cell object (TableCell interface for AT-SPI,
IAccessibleTableCell for IAccessible2).

Those interfaces are already implemented/exposed
for winaccessibility (s. `CAccTable`) and the
qt5/qt6/kf5 VCL plugins (s. the `QAccessibleTableCellInterface`
methods implemented in `QtAccessibleInterface`).
With the switch to 64-bit internal a11y child indices,
these now behave correctly for cells with a child
index that doesn't fit into 32 bit as well.

NVDA on Windows already uses the IAccessibleTableCell
interface and thus announcing focused cells works fine
with this change in place.

Orca on Linux currently doesn't make use of the AT-SPI
TableCell interface yet, but with a suggested change to
do so [1], announcement of selected cells works
with the qt6 VCL plugin with a current qtbase dev branch
as well - when combined with the suggested changes
to implement support for the AT-SPI TableCell interface
in Qt [2] [3] and the LO change based on that [4] and
a fix for a nullptr dereference [5].

The gtk3 VCL plugin doesn't expose the AT-SPI
TableCell interface yet, but once it does so
(via `AtkTableCell`), it also works with the
suggested Orca change [1] in place.
(Adding that is planned for an upcoming change,
works with a local WIP patch.)

For handling return values that are larger than what
platform APIs support, the following approach has
been chosen for now:

1) When the return value is for the count of
(selected) children, the maximum value N
supported by the platform API is returned.
(This is what `ScAccessibleTableBase::getAccessibleChildCount`
did previously.)
The first N elements can be accessed by their
actual (selection) indices.

2) When the return value is the child/cell index,
-2 is returned for objects whose index is greater
than the maximum value supported by the platform
API.
Using a non-negative value would mean that the
index would refer to *another* actually existing
child. A child index of -1 on the other hand
tends to be interpreted as "child is invalid" or
"object isn't actually a child of its (previous)
parent any more)". For the Orca case, this would
result in objects with a child index of -1
not being announced, as they are considered
"zombies" [6].

What's still somewhat problematic is the case where
more than 2^31 children are *selected*, since access
to those children still happens by the index into
the selection in the platform APIs, and not all
selected items are accessible this way.
(Screen readers usually just retrieve
the first and last element from the selection and
announce those.)

Orca already seems to apply different handling for the
case for fully selected rows and columns, so
"All cells selected" or "Columns ... to ... selected"
is announced just fine even if more than 2^31
cells are selected.

(Side note: While Microsoft User Interface
Automation - UIA - also uses 32-bit indices, it also
has specific methods in the ISelectionProvider2
interface that allow to explicitly retrieve the
first and last selected item,
`ISelectionProvider2::get_FirstSelectedItem` and
`ISelectionProvider2::get_LastSelectedItem`, but
we currently don't support UIA on Windows.)

Bound checks at the beginning of the methods from the
`XAccessibleContext`, `XAccessibleSelection` and
`XAccessibleTable` interfaces that take a child index
(or in helper methods called by those) should generally
already prevent too large indices from being passed to
the methods in the lower layer code that take smaller
integer types. Such bound checking has been
been added in various places where it wasn't present yet.
If there any remaining issues of this
kind that show after this commit, they can probably be
solved in a similar way (s.e.g. the change to
`AccessibleBrowseBox::getAccessibleChild` in this
commit).
A few asserts were also added at
places where my understanding is that values shouldn't
be larger than what is supported by a called method
anyway.

A test case will be added in a following change.

[1] https://gitlab.gnome.org/GNOME/orca/-/merge_requests/131
[2] https://codereview.qt-project.org/c/qt/qtbase/+/428566
[3] https://codereview.qt-project.org/c/qt/qtbase/+/428567
[4] https://gerrit.libreoffice.org/c/core/+/138750
[5] https://codereview.qt-project.org/c/qt/qtbase/+/430157
[6] 82c8542002/src/orca/script_utilities.py (L5155)

Change-Id: I3af590c988b0e6754fc72545918412f39e8fea07
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139258
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
2022-09-02 15:47:37 +02:00

966 lines
34 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// Global header
#include <utility>
#include <memory>
#include <vector>
#include <algorithm>
#include <rtl/ustrbuf.hxx>
#include <tools/debug.hxx>
#include <vcl/svapp.hxx>
#include <comphelper/sequence.hxx>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/uno/Reference.hxx>
#include <com/sun/star/awt/Point.hpp>
#include <com/sun/star/awt/Rectangle.hpp>
#include <com/sun/star/accessibility/AccessibleTextType.hpp>
// Project-local header
#include <editeng/editdata.hxx>
#include <editeng/unoedprx.hxx>
#include <editeng/AccessibleStaticTextBase.hxx>
#include <editeng/AccessibleEditableTextPara.hxx>
using namespace ::com::sun::star;
using namespace ::com::sun::star::accessibility;
/* TODO:
=====
- separate adapter functionality from AccessibleStaticText class
- refactor common loops into templates, using mem_fun
*/
namespace accessibility
{
typedef std::vector< beans::PropertyValue > PropertyValueVector;
namespace {
class PropertyValueEqualFunctor
{
const beans::PropertyValue& m_rPValue;
public:
explicit PropertyValueEqualFunctor(const beans::PropertyValue& rPValue)
: m_rPValue(rPValue)
{}
bool operator() ( const beans::PropertyValue& rhs ) const
{
return ( m_rPValue.Name == rhs.Name && m_rPValue.Value == rhs.Value );
}
};
}
sal_Unicode const cNewLine(0x0a);
// Static Helper
static ESelection MakeSelection( sal_Int32 nStartPara, sal_Int32 nStartIndex,
sal_Int32 nEndPara, sal_Int32 nEndIndex )
{
DBG_ASSERT(nStartPara >= 0 &&
nStartIndex >= 0 &&
nEndPara >= 0 &&
nEndIndex >= 0,
"AccessibleStaticTextBase_Impl::MakeSelection: index value overflow");
return ESelection(nStartPara, nStartIndex, nEndPara, nEndIndex);
}
// AccessibleStaticTextBase_Impl declaration
/** AccessibleStaticTextBase_Impl
This class implements the AccessibleStaticTextBase
functionality, mainly by forwarding the calls to an aggregated
AccessibleEditableTextPara. As this is a therefore non-trivial
adapter, factoring out the common functionality from
AccessibleEditableTextPara might be a profitable future task.
*/
class AccessibleStaticTextBase_Impl
{
friend class AccessibleStaticTextBase;
public:
// receive pointer to our frontend class and view window
AccessibleStaticTextBase_Impl();
void SetEditSource( std::unique_ptr< SvxEditSource > && pEditSource );
void SetEventSource( const uno::Reference< XAccessible >& rInterface )
{
mxThis = rInterface;
}
void SetOffset( const Point& );
void Dispose();
AccessibleEditableTextPara& GetParagraph( sal_Int32 nPara ) const;
sal_Int32 GetParagraphCount() const;
EPosition Index2Internal( sal_Int32 nFlatIndex ) const
{
return ImpCalcInternal( nFlatIndex, false );
}
EPosition Range2Internal( sal_Int32 nFlatIndex ) const
{
return ImpCalcInternal( nFlatIndex, true );
}
sal_Int32 Internal2Index( EPosition nEEIndex ) const;
void CorrectTextSegment( TextSegment& aTextSegment,
int nPara ) const;
bool SetSelection( sal_Int32 nStartPara, sal_Int32 nStartIndex,
sal_Int32 nEndPara, sal_Int32 nEndIndex );
bool CopyText( sal_Int32 nStartPara, sal_Int32 nStartIndex,
sal_Int32 nEndPara, sal_Int32 nEndIndex );
tools::Rectangle GetParagraphBoundingBox() const;
bool RemoveLineBreakCount( sal_Int32& rIndex );
private:
EPosition ImpCalcInternal( sal_Int32 nFlatIndex, bool bExclusive ) const;
// our frontend class (the one implementing the actual
// interface). That's not necessarily the one containing the impl
// pointer
uno::Reference< XAccessible > mxThis;
// implements our functionality, we're just an adapter (guarded by solar mutex)
mutable rtl::Reference<AccessibleEditableTextPara> mxTextParagraph;
// a wrapper for the text forwarders (guarded by solar mutex)
mutable SvxEditSourceAdapter maEditSource;
};
// AccessibleStaticTextBase_Impl implementation
AccessibleStaticTextBase_Impl::AccessibleStaticTextBase_Impl() :
mxTextParagraph( new AccessibleEditableTextPara(nullptr) )
{
// TODO: this is still somewhat of a hack, all the more since
// now the maTextParagraph has an empty parent reference set
}
void AccessibleStaticTextBase_Impl::SetEditSource( std::unique_ptr< SvxEditSource > && pEditSource )
{
maEditSource.SetEditSource( std::move(pEditSource) );
if( mxTextParagraph.is() )
mxTextParagraph->SetEditSource( &maEditSource );
}
void AccessibleStaticTextBase_Impl::SetOffset( const Point& rPoint )
{
if( mxTextParagraph.is() )
mxTextParagraph->SetEEOffset( rPoint );
}
void AccessibleStaticTextBase_Impl::Dispose()
{
// we're the owner of the paragraph, so destroy it, too
if( mxTextParagraph.is() )
mxTextParagraph->Dispose();
// drop references
mxThis = nullptr;
mxTextParagraph.clear();
}
AccessibleEditableTextPara& AccessibleStaticTextBase_Impl::GetParagraph( sal_Int32 nPara ) const
{
if( !mxTextParagraph.is() )
throw lang::DisposedException ("object has been already disposed", mxThis );
// TODO: Have a different method on AccessibleEditableTextPara
// that does not care about state changes
mxTextParagraph->SetParagraphIndex( nPara );
return *mxTextParagraph;
}
sal_Int32 AccessibleStaticTextBase_Impl::GetParagraphCount() const
{
if( !mxTextParagraph.is() )
return 0;
else
return mxTextParagraph->GetTextForwarder().GetParagraphCount();
}
sal_Int32 AccessibleStaticTextBase_Impl::Internal2Index( EPosition nEEIndex ) const
{
// XXX checks for overflow and returns maximum if so
sal_Int32 aRes(0);
for(sal_Int32 i=0; i<nEEIndex.nPara; ++i)
{
sal_Int32 nCount = GetParagraph(i).getCharacterCount();
if (SAL_MAX_INT32 - aRes > nCount)
return SAL_MAX_INT32;
aRes += nCount;
}
if (SAL_MAX_INT32 - aRes > nEEIndex.nIndex)
return SAL_MAX_INT32;
return aRes + nEEIndex.nIndex;
}
void AccessibleStaticTextBase_Impl::CorrectTextSegment( TextSegment& aTextSegment,
int nPara ) const
{
// Keep 'invalid' values at the TextSegment
if( aTextSegment.SegmentStart != -1 &&
aTextSegment.SegmentEnd != -1 )
{
// #112814# Correct TextSegment by paragraph offset
sal_Int32 nOffset(0);
int i;
for(i=0; i<nPara; ++i)
nOffset += GetParagraph(i).getCharacterCount();
aTextSegment.SegmentStart += nOffset;
aTextSegment.SegmentEnd += nOffset;
}
}
EPosition AccessibleStaticTextBase_Impl::ImpCalcInternal( sal_Int32 nFlatIndex, bool bExclusive ) const
{
if( nFlatIndex < 0 )
throw lang::IndexOutOfBoundsException("AccessibleStaticTextBase_Impl::Index2Internal: character index out of bounds",
mxThis);
// gratuitously accepting larger indices here, AccessibleEditableTextPara will throw eventually
sal_Int32 nCurrPara, nCurrIndex, nParas, nCurrCount;
for( nCurrPara=0, nParas=GetParagraphCount(), nCurrCount=0, nCurrIndex=0; nCurrPara<nParas; ++nCurrPara )
{
nCurrCount = GetParagraph( nCurrPara ).getCharacterCount();
nCurrIndex += nCurrCount;
if( nCurrIndex >= nFlatIndex )
{
// check overflow
DBG_ASSERT(nCurrPara >= 0 &&
nFlatIndex - nCurrIndex + nCurrCount >= 0,
"AccessibleStaticTextBase_Impl::Index2Internal: index value overflow");
return EPosition(nCurrPara, nFlatIndex - nCurrIndex + nCurrCount);
}
}
// #102170# Allow one-past the end for ranges
if( bExclusive && nCurrIndex == nFlatIndex )
{
// check overflow
DBG_ASSERT(nCurrPara > 0 &&
nFlatIndex - nCurrIndex + nCurrCount >= 0,
"AccessibleStaticTextBase_Impl::Index2Internal: index value overflow");
return EPosition(nCurrPara-1, nFlatIndex - nCurrIndex + nCurrCount);
}
// not found? Out of bounds
throw lang::IndexOutOfBoundsException("AccessibleStaticTextBase_Impl::Index2Internal: character index out of bounds",
mxThis);
}
bool AccessibleStaticTextBase_Impl::SetSelection( sal_Int32 nStartPara, sal_Int32 nStartIndex,
sal_Int32 nEndPara, sal_Int32 nEndIndex )
{
if( !mxTextParagraph.is() )
return false;
try
{
SvxEditViewForwarder& rCacheVF = mxTextParagraph->GetEditViewForwarder( true );
return rCacheVF.SetSelection( MakeSelection(nStartPara, nStartIndex, nEndPara, nEndIndex) );
}
catch( const uno::RuntimeException& )
{
return false;
}
}
bool AccessibleStaticTextBase_Impl::CopyText( sal_Int32 nStartPara, sal_Int32 nStartIndex,
sal_Int32 nEndPara, sal_Int32 nEndIndex )
{
if( !mxTextParagraph.is() )
return false;
try
{
SvxEditViewForwarder& rCacheVF = mxTextParagraph->GetEditViewForwarder( true );
mxTextParagraph->GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs
bool aRetVal;
// save current selection
ESelection aOldSelection;
rCacheVF.GetSelection( aOldSelection );
rCacheVF.SetSelection( MakeSelection(nStartPara, nStartIndex, nEndPara, nEndIndex) );
aRetVal = rCacheVF.Copy();
rCacheVF.SetSelection( aOldSelection ); // restore
return aRetVal;
}
catch( const uno::RuntimeException& )
{
return false;
}
}
tools::Rectangle AccessibleStaticTextBase_Impl::GetParagraphBoundingBox() const
{
tools::Rectangle aRect;
if( mxTextParagraph.is() )
{
awt::Rectangle aAwtRect = mxTextParagraph->getBounds();
aRect = tools::Rectangle( Point( aAwtRect.X, aAwtRect.Y ), Size( aAwtRect.Width, aAwtRect.Height ) );
}
else
{
aRect.SetEmpty();
}
return aRect;
}
//the input argument is the index(including "\n" ) in the string.
//the function will calculate the actual index(not including "\n") in the string.
//and return true if the index is just at a "\n"
bool AccessibleStaticTextBase_Impl::RemoveLineBreakCount( sal_Int32& rIndex )
{
// get the total char number inside the cell.
sal_Int32 i, nCount, nParas;
for( i=0, nCount=0, nParas=GetParagraphCount(); i<nParas; ++i )
nCount += GetParagraph(i).getCharacterCount();
nCount = nCount + (nParas-1);
if( nCount == 0 && rIndex == 0) return false;
sal_Int32 nCurrPara, nCurrCount;
sal_Int32 nLineBreakPos = 0, nLineBreakCount = 0;
sal_Int32 nParaCount = GetParagraphCount();
for ( nCurrCount = 0, nCurrPara = 0; nCurrPara < nParaCount; nCurrPara++ )
{
nCurrCount += GetParagraph( nCurrPara ).getCharacterCount();
nLineBreakPos = nCurrCount++;
if ( rIndex == nLineBreakPos )
{
rIndex -= (++nLineBreakCount);//(++nLineBreakCount);
if ( rIndex < 0)
{
rIndex = 0;
}
//if the index is at the last position of the last paragraph
//there is no "\n" , so we should increase rIndex by 1 and return false.
if ( (nCurrPara+1) == nParaCount )
{
rIndex++;
return false;
}
else
{
return true;
}
}
else if ( rIndex < nLineBreakPos )
{
rIndex -= nLineBreakCount;
return false;
}
else
{
nLineBreakCount++;
}
}
return false;
}
// AccessibleStaticTextBase implementation
AccessibleStaticTextBase::AccessibleStaticTextBase( std::unique_ptr< SvxEditSource > && pEditSource ) :
mpImpl( new AccessibleStaticTextBase_Impl() )
{
SolarMutexGuard aGuard;
SetEditSource( std::move(pEditSource) );
}
AccessibleStaticTextBase::~AccessibleStaticTextBase()
{
}
void AccessibleStaticTextBase::SetEditSource( std::unique_ptr< SvxEditSource > && pEditSource )
{
// precondition: solar mutex locked
DBG_TESTSOLARMUTEX();
mpImpl->SetEditSource( std::move(pEditSource) );
}
void AccessibleStaticTextBase::SetEventSource( const uno::Reference< XAccessible >& rInterface )
{
mpImpl->SetEventSource( rInterface );
}
void AccessibleStaticTextBase::SetOffset( const Point& rPoint )
{
// precondition: solar mutex locked
DBG_TESTSOLARMUTEX();
mpImpl->SetOffset( rPoint );
}
void AccessibleStaticTextBase::Dispose()
{
mpImpl->Dispose();
}
// XAccessibleContext
sal_Int64 AccessibleStaticTextBase::getAccessibleChildCount()
{
// no children at all
return 0;
}
uno::Reference< XAccessible > AccessibleStaticTextBase::getAccessibleChild( sal_Int64 /*i*/ )
{
// no children at all
return uno::Reference< XAccessible >();
}
uno::Reference< XAccessible > AccessibleStaticTextBase::getAccessibleAtPoint( const awt::Point& /*_aPoint*/ )
{
// no children at all
return uno::Reference< XAccessible >();
}
// XAccessibleText
sal_Int32 SAL_CALL AccessibleStaticTextBase::getCaretPosition()
{
SolarMutexGuard aGuard;
sal_Int32 i, nPos, nParas;
for( i=0, nPos=-1, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i )
{
if( (nPos=mpImpl->GetParagraph(i).getCaretPosition()) != -1 )
return nPos;
}
return nPos;
}
sal_Bool SAL_CALL AccessibleStaticTextBase::setCaretPosition( sal_Int32 nIndex )
{
return setSelection(nIndex, nIndex);
}
sal_Unicode SAL_CALL AccessibleStaticTextBase::getCharacter( sal_Int32 nIndex )
{
SolarMutexGuard aGuard;
EPosition aPos( mpImpl->Index2Internal(nIndex) );
return mpImpl->GetParagraph( aPos.nPara ).getCharacter( aPos.nIndex );
}
uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleStaticTextBase::getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes )
{
SolarMutexGuard aGuard;
//get the actual index without "\n"
mpImpl->RemoveLineBreakCount( nIndex );
EPosition aPos( mpImpl->Index2Internal(nIndex) );
return mpImpl->GetParagraph( aPos.nPara ).getCharacterAttributes( aPos.nIndex, aRequestedAttributes );
}
awt::Rectangle SAL_CALL AccessibleStaticTextBase::getCharacterBounds( sal_Int32 nIndex )
{
SolarMutexGuard aGuard;
// #108900# Allow ranges for nIndex, as one-past-the-end
// values are now legal, too.
EPosition aPos( mpImpl->Range2Internal(nIndex) );
// #i70916# Text in spread sheet cells return the wrong extents
AccessibleEditableTextPara& rPara = mpImpl->GetParagraph( aPos.nPara );
awt::Rectangle aParaBounds( rPara.getBounds() );
awt::Rectangle aBounds( rPara.getCharacterBounds( aPos.nIndex ) );
aBounds.X += aParaBounds.X;
aBounds.Y += aParaBounds.Y;
return aBounds;
}
sal_Int32 SAL_CALL AccessibleStaticTextBase::getCharacterCount()
{
SolarMutexGuard aGuard;
sal_Int32 i, nCount, nParas;
for( i=0, nCount=0, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i )
nCount += mpImpl->GetParagraph(i).getCharacterCount();
//count on the number of "\n" which equals number of paragraphs decrease 1.
nCount = nCount + (nParas-1);
return nCount;
}
sal_Int32 SAL_CALL AccessibleStaticTextBase::getIndexAtPoint( const awt::Point& rPoint )
{
SolarMutexGuard aGuard;
const sal_Int32 nParas( mpImpl->GetParagraphCount() );
sal_Int32 nIndex;
int i;
for( i=0; i<nParas; ++i )
{
// TODO: maybe exploit the fact that paragraphs are
// ordered vertically for early exit
// #i70916# Text in spread sheet cells return the wrong extents
AccessibleEditableTextPara& rPara = mpImpl->GetParagraph( i );
awt::Rectangle aParaBounds( rPara.getBounds() );
awt::Point aPoint( rPoint );
aPoint.X -= aParaBounds.X;
aPoint.Y -= aParaBounds.Y;
// #112814# Use correct index offset
if ( ( nIndex = rPara.getIndexAtPoint( aPoint ) ) != -1 )
return mpImpl->Internal2Index(EPosition(i, nIndex));
}
return -1;
}
OUString SAL_CALL AccessibleStaticTextBase::getSelectedText()
{
SolarMutexGuard aGuard;
sal_Int32 nStart( getSelectionStart() );
sal_Int32 nEnd( getSelectionEnd() );
// #104481# Return the empty string for 'no selection'
if( nStart < 0 || nEnd < 0 )
return OUString();
return getTextRange( nStart, nEnd );
}
sal_Int32 SAL_CALL AccessibleStaticTextBase::getSelectionStart()
{
SolarMutexGuard aGuard;
sal_Int32 i, nPos, nParas;
for( i=0, nPos=-1, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i )
{
if( (nPos=mpImpl->GetParagraph(i).getSelectionStart()) != -1 )
return nPos;
}
return nPos;
}
sal_Int32 SAL_CALL AccessibleStaticTextBase::getSelectionEnd()
{
SolarMutexGuard aGuard;
sal_Int32 i, nPos, nParas;
for( i=0, nPos=-1, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i )
{
if( (nPos=mpImpl->GetParagraph(i).getSelectionEnd()) != -1 )
return nPos;
}
return nPos;
}
sal_Bool SAL_CALL AccessibleStaticTextBase::setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
{
SolarMutexGuard aGuard;
EPosition aStartIndex( mpImpl->Range2Internal(nStartIndex) );
EPosition aEndIndex( mpImpl->Range2Internal(nEndIndex) );
return mpImpl->SetSelection( aStartIndex.nPara, aStartIndex.nIndex,
aEndIndex.nPara, aEndIndex.nIndex );
}
OUString SAL_CALL AccessibleStaticTextBase::getText()
{
SolarMutexGuard aGuard;
sal_Int32 i, nParas;
OUStringBuffer aRes;
for( i=0, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i )
aRes.append(mpImpl->GetParagraph(i).getText());
return aRes.makeStringAndClear();
}
OUString SAL_CALL AccessibleStaticTextBase::getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
{
SolarMutexGuard aGuard;
if( nStartIndex > nEndIndex )
std::swap(nStartIndex, nEndIndex);
//if startindex equals endindex we will get nothing. So return an empty string directly.
if ( nStartIndex == nEndIndex )
{
return OUString();
}
bool bStart = mpImpl->RemoveLineBreakCount( nStartIndex );
//if the start index is just at a "\n", we need to begin from the next char
if ( bStart )
{
nStartIndex++;
}
//we need to find out whether the previous position of the current endindex is at "\n" or not
//if yes we need to mark it and add "\n" at the end of the result
sal_Int32 nTemp = nEndIndex - 1;
bool bEnd = mpImpl->RemoveLineBreakCount( nTemp );
bool bTemp = mpImpl->RemoveLineBreakCount( nEndIndex );
//if the below condition is true it indicates an empty paragraph with just a "\n"
//so we need to set one "\n" flag to avoid duplication.
if ( bStart && bEnd && ( nStartIndex == nEndIndex) )
{
bEnd = false;
}
//if the current endindex is at a "\n", we need to increase endindex by 1 to make sure
//the char before "\n" is included. Because string returned by this function will not include
//the char at the endindex.
if ( bTemp )
{
nEndIndex++;
}
OUStringBuffer aRes;
EPosition aStartIndex( mpImpl->Range2Internal(nStartIndex) );
EPosition aEndIndex( mpImpl->Range2Internal(nEndIndex) );
// #102170# Special case: start and end paragraph are identical
if( aStartIndex.nPara == aEndIndex.nPara )
{
//we don't return the string directly now for that we have to do some further process for "\n"
aRes = mpImpl->GetParagraph( aStartIndex.nPara ).getTextRange( aStartIndex.nIndex, aEndIndex.nIndex );
}
else
{
sal_Int32 i( aStartIndex.nPara );
aRes = mpImpl->GetParagraph(i).getTextRange( aStartIndex.nIndex,
mpImpl->GetParagraph(i).getCharacterCount()/*-1*/);
++i;
// paragraphs inbetween are fully included
for( ; i<aEndIndex.nPara; ++i )
{
aRes.append(cNewLine);
aRes.append(mpImpl->GetParagraph(i).getText());
}
if( i<=aEndIndex.nPara )
{
//if the below condition is matched it means that endindex is at mid of the last paragraph
//we need to add a "\n" before we add the last part of the string.
if ( !bEnd && aEndIndex.nIndex )
{
aRes.append(cNewLine);
}
aRes.append(mpImpl->GetParagraph(i).getTextRange( 0, aEndIndex.nIndex ));
}
}
//According to the flag we marked before, we have to add "\n" at the beginning
//or at the end of the result string.
if ( bStart )
{
aRes.insert(0, OUStringChar(cNewLine));
}
if ( bEnd )
{
aRes.append(OUStringChar(cNewLine));
}
return aRes.makeStringAndClear();
}
css::accessibility::TextSegment SAL_CALL AccessibleStaticTextBase::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType )
{
SolarMutexGuard aGuard;
bool bLineBreak = mpImpl->RemoveLineBreakCount( nIndex );
EPosition aPos( mpImpl->Range2Internal(nIndex) );
css::accessibility::TextSegment aResult;
if( AccessibleTextType::PARAGRAPH == aTextType )
{
// #106393# Special casing one behind last paragraph is
// not necessary, since then, we return the content and
// boundary of that last paragraph. Range2Internal is
// tolerant against that, and returns the last paragraph
// in aPos.nPara.
// retrieve full text of the paragraph
aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara ).getText();
// #112814# Adapt the start index with the paragraph offset
aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara, 0 ) );
aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength();
}
else if ( AccessibleTextType::ATTRIBUTE_RUN == aTextType )
{
SvxAccessibleTextAdapter& rTextForwarder = mpImpl->GetParagraph( aPos.nIndex ).GetTextForwarder();
sal_Int32 nStartIndex, nEndIndex;
if ( rTextForwarder.GetAttributeRun( nStartIndex, nEndIndex, aPos.nPara, aPos.nIndex, true ) )
{
aResult.SegmentText = getTextRange( nStartIndex, nEndIndex );
aResult.SegmentStart = nStartIndex;
aResult.SegmentEnd = nEndIndex;
}
}
else
{
// No special handling required, forward to wrapped class
aResult = mpImpl->GetParagraph( aPos.nPara ).getTextAtIndex( aPos.nIndex, aTextType );
// #112814# Adapt the start index with the paragraph offset
mpImpl->CorrectTextSegment( aResult, aPos.nPara );
if ( bLineBreak )
{
aResult.SegmentText = OUString(cNewLine);
}
}
return aResult;
}
css::accessibility::TextSegment SAL_CALL AccessibleStaticTextBase::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType )
{
SolarMutexGuard aGuard;
sal_Int32 nOldIdx = nIndex;
bool bLineBreak = mpImpl->RemoveLineBreakCount( nIndex );
EPosition aPos( mpImpl->Range2Internal(nIndex) );
css::accessibility::TextSegment aResult;
if( AccessibleTextType::PARAGRAPH == aTextType )
{
if( aPos.nIndex == mpImpl->GetParagraph( aPos.nPara ).getCharacterCount() )
{
// #103589# Special casing one behind the last paragraph
aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara ).getText();
// #112814# Adapt the start index with the paragraph offset
aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara, 0 ) );
}
else if( aPos.nPara > 0 )
{
aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara - 1 ).getText();
// #112814# Adapt the start index with the paragraph offset
aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara - 1, 0 ) );
}
aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength();
}
else
{
// No special handling required, forward to wrapped class
aResult = mpImpl->GetParagraph( aPos.nPara ).getTextBeforeIndex( aPos.nIndex, aTextType );
// #112814# Adapt the start index with the paragraph offset
mpImpl->CorrectTextSegment( aResult, aPos.nPara );
if ( bLineBreak && (nOldIdx-1) >= 0)
{
aResult = getTextAtIndex( nOldIdx-1, aTextType );
}
}
return aResult;
}
css::accessibility::TextSegment SAL_CALL AccessibleStaticTextBase::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType )
{
SolarMutexGuard aGuard;
sal_Int32 nTemp = nIndex+1;
bool bLineBreak = mpImpl->RemoveLineBreakCount( nTemp );
mpImpl->RemoveLineBreakCount( nIndex );
EPosition aPos( mpImpl->Range2Internal(nIndex) );
css::accessibility::TextSegment aResult;
if( AccessibleTextType::PARAGRAPH == aTextType )
{
// Special casing one behind the last paragraph is not
// necessary, this case is invalid here for
// getTextBehindIndex
if( aPos.nPara + 1 < mpImpl->GetParagraphCount() )
{
aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara + 1 ).getText();
// #112814# Adapt the start index with the paragraph offset
aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara + 1, 0 ) );
aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength();
}
}
else
{
// No special handling required, forward to wrapped class
aResult = mpImpl->GetParagraph( aPos.nPara ).getTextBehindIndex( aPos.nIndex, aTextType );
// #112814# Adapt the start index with the paragraph offset
mpImpl->CorrectTextSegment( aResult, aPos.nPara );
if ( bLineBreak )
{
aResult.SegmentText = OUStringChar(cNewLine) + aResult.SegmentText;
}
}
return aResult;
}
sal_Bool SAL_CALL AccessibleStaticTextBase::copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
{
SolarMutexGuard aGuard;
if( nStartIndex > nEndIndex )
std::swap(nStartIndex, nEndIndex);
EPosition aStartIndex( mpImpl->Range2Internal(nStartIndex) );
EPosition aEndIndex( mpImpl->Range2Internal(nEndIndex) );
return mpImpl->CopyText( aStartIndex.nPara, aStartIndex.nIndex,
aEndIndex.nPara, aEndIndex.nIndex );
}
sal_Bool SAL_CALL AccessibleStaticTextBase::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType )
{
return false;
}
// XAccessibleTextAttributes
uno::Sequence< beans::PropertyValue > AccessibleStaticTextBase::getDefaultAttributes( const uno::Sequence< OUString >& RequestedAttributes )
{
// get the intersection of the default attributes of all paragraphs
SolarMutexGuard aGuard;
PropertyValueVector aDefAttrVec(
comphelper::sequenceToContainer<PropertyValueVector>(mpImpl->GetParagraph( 0 ).getDefaultAttributes( RequestedAttributes )) );
const sal_Int32 nParaCount = mpImpl->GetParagraphCount();
for ( sal_Int32 nPara = 1; nPara < nParaCount; ++nPara )
{
uno::Sequence< beans::PropertyValue > aSeq = mpImpl->GetParagraph( nPara ).getDefaultAttributes( RequestedAttributes );
PropertyValueVector aIntersectionVec;
for ( const auto& rDefAttr : aDefAttrVec )
{
const beans::PropertyValue* pItr = aSeq.getConstArray();
const beans::PropertyValue* pEnd = pItr + aSeq.getLength();
const beans::PropertyValue* pFind = std::find_if( pItr, pEnd, PropertyValueEqualFunctor(rDefAttr) );
if ( pFind != pEnd )
{
aIntersectionVec.push_back( *pFind );
}
}
aDefAttrVec.swap( aIntersectionVec );
if ( aDefAttrVec.empty() )
{
break;
}
}
return comphelper::containerToSequence(aDefAttrVec);
}
uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleStaticTextBase::getRunAttributes( sal_Int32 nIndex, const uno::Sequence< OUString >& RequestedAttributes )
{
// get those default attributes of the paragraph, which are not part
// of the intersection of all paragraphs and add them to the run attributes
SolarMutexGuard aGuard;
EPosition aPos( mpImpl->Index2Internal( nIndex ) );
AccessibleEditableTextPara& rPara = mpImpl->GetParagraph( aPos.nPara );
uno::Sequence< beans::PropertyValue > aDefAttrSeq = rPara.getDefaultAttributes( RequestedAttributes );
uno::Sequence< beans::PropertyValue > aRunAttrSeq = rPara.getRunAttributes( aPos.nIndex, RequestedAttributes );
uno::Sequence< beans::PropertyValue > aIntersectionSeq = getDefaultAttributes( RequestedAttributes );
PropertyValueVector aDiffVec;
const beans::PropertyValue* pDefAttr = aDefAttrSeq.getConstArray();
const sal_Int32 nLength = aDefAttrSeq.getLength();
for ( sal_Int32 i = 0; i < nLength; ++i )
{
const beans::PropertyValue* pItr = aIntersectionSeq.getConstArray();
const beans::PropertyValue* pEnd = pItr + aIntersectionSeq.getLength();
bool bNone = std::none_of( pItr, pEnd, PropertyValueEqualFunctor( pDefAttr[i] ) );
if ( bNone && pDefAttr[i].Handle != 0)
{
aDiffVec.push_back( pDefAttr[i] );
}
}
return ::comphelper::concatSequences( aRunAttrSeq, comphelper::containerToSequence(aDiffVec) );
}
tools::Rectangle AccessibleStaticTextBase::GetParagraphBoundingBox() const
{
return mpImpl->GetParagraphBoundingBox();
}
} // end of namespace accessibility
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */