/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * 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/.
*/
namespace
{ /** @brief Helper class to check text attributes are properly exported to Atspi. * * This kind of duplicates most of the logic in atktextattributes.cxx, but if we want to check the * values are correct (which includes whether they are properly updated for example), we have to do * this, even though it means quite some processing for some of the attributes.
* This has to be kept in sync with how atktextattributes.cxx exposes those attributes. */ class AttributesChecker
{ private:
uno::Reference<accessibility::XAccessibleText> mxLOText;
Atspi::Text mxAtspiText;
private: // helper to validate a value represented as a single float in ATSPI staticbool implCheckFloat(std::string_view atspiValue, float expected)
{ float f; char dummy;
bool checkColor(std::string_view atspiValue, const beans::PropertyValue& property, const uno::Sequence<beans::PropertyValue>&)
{ auto color = property.Value.get<sal_Int32>();
if (color == -1) // automatic, use the component's color
{
uno::Reference<accessibility::XAccessibleComponent> xComponent(mxLOText,
uno::UNO_QUERY); if (xComponent.is())
{ if (property.Name == u"CharBackColor")
color = xComponent->getBackground(); elseif (property.Name == u"CharColor")
color = xComponent->getForeground();
}
}
// CMM is 1/100th of a mm
CPPUNIT_ASSERT_EQUAL(1, sscanf(atspiValue.data(), "%lgmm%c", &v, &dummy));
CPPUNIT_ASSERT_DOUBLES_EQUAL(property.Value.get<sal_Int32>() * 0.01, v, 1e-4);
// same as findProperty() above, but with a fast path is @p property is a match staticconst beans::PropertyValue*
findProperty(const beans::PropertyValue* property, const uno::Sequence<beans::PropertyValue>& properties, std::u16string_view name)
{ if (property->Name == name) return property; return findProperty(properties, name);
}
for (constauto& prop : xLOAttributeList)
{
std::cout << "found run attribute: " << prop.Name << "=" << prop.Value << std::endl;
/* we need to loop on all entries because there might be more than one for a single
* property */ for (constauto& entry : atspiMap)
{ if (!prop.Name.equalsAscii(entry.loName)) continue;
constauto atspiIter = xAtspiAttributeList.find(entry.atspiName); /* we use an empty value if there isn't one, which can happen if the value cannot
* be represented by Atspi, or if the actual LO value is also empty */
std::string atspiValue; if (atspiIter != xAtspiAttributeList.end())
atspiValue = atspiIter->second;
/* LO doesn't implement it itself, but ATK provides a fallback. Add a test here merely for the * future when we have a direct AT-SPI implementation for e.g. GTK4. * Just like atk-adaptor, we compute the bounding box by combining extents for each character
* in the range */ static awt::Rectangle getRangeBounds(const uno::Reference<accessibility::XAccessibleText>& xText,
sal_Int32 startOffset, sal_Int32 endOffset)
{
awt::Rectangle bounds;
// text run attributes
uno::Reference<accessibility::XAccessibleTextMarkup> xTextMarkup(xLOText, uno::UNO_QUERY); while (offset < characterCount)
{ // message for the assertions so we know where it comes from
OString offsetMsg(OString::Concat("in ") + AccessibilityTools::debugString(xLOText).c_str()
+ " at offset " + OString::number(offset));
// loop over each character for (offset = 0; offset < characterCount;)
{ constauto aTextSegment
= xLOText->getTextAtIndex(offset, accessibility::AccessibleTextType::CHARACTER);
OString offsetMsg(OString::Concat("in ") + AccessibilityTools::debugString(xLOText).c_str()
+ " at offset " + OString::number(offset));
// getCharacterAtOffset()
sal_Int32 nChOffset = 0;
sal_Int32 cp = aTextSegment.SegmentText.iterateCodePoints(&nChOffset); /* do not check unpaired surrogates, because they are unlikely to make any sense and LO's
* GTK VCL doesn't like them */ if (!rtl::isSurrogate(cp))
CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), cp,
pAtspiText.getCharacterAtOffset(offset));
// for WORD there's adjustments to be made, see atktext.cxx:adjust_boundaries() if (pair.loTextType == accessibility::AccessibleTextType::WORD
&& !loTextSegment.SegmentText.isEmpty())
{ // Determine the start index of the next segment constauto loTextSegmentBehind
= xLOText->getTextBehindIndex(loTextSegment.SegmentEnd, pair.loTextType); if (!loTextSegmentBehind.SegmentText.isEmpty())
loTextSegment.SegmentEnd = loTextSegmentBehind.SegmentStart; else
loTextSegment.SegmentEnd = xLOText->getCharacterCount();
OString boundaryMsg(offsetMsg + " with boundary type "
+ Atspi::TextBoundaryType::getName(pair.atspiBoundaryType).c_str());
CPPUNIT_ASSERT_EQUAL_MESSAGE(boundaryMsg.getStr(), loTextSegment.SegmentText,
OUString::fromUtf8(atspiTextRange.content)); /* if the segment is empty, LO API gives -1 offsets, but maps to 0 for AT-SPI. This is
* fine, AT-SPI doesn't really say what the offsets should be when the text is empty */ if (!loTextSegment.SegmentText.isEmpty())
{
CPPUNIT_ASSERT_EQUAL_MESSAGE(boundaryMsg.getStr(), loTextSegment.SegmentStart,
sal_Int32(atspiTextRange.startOffset));
CPPUNIT_ASSERT_EQUAL_MESSAGE(boundaryMsg.getStr(), loTextSegment.SegmentEnd,
sal_Int32(atspiTextRange.endOffset));
}
}
// character bounds constauto loRect = xLOText->getCharacterBounds(offset); auto atspiRect = pAtspiText.getCharacterExtents(offset, ATSPI_COORD_TYPE_WINDOW);
atspiRect.x -= atspiPosition.x;
atspiRect.y -= atspiPosition.y;
CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), loRect.Y, sal_Int32(atspiRect.y));
CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), loRect.Height,
sal_Int32(atspiRect.height)); /* for some reason getCharacterBounds() might return negative widths in some cases * (including a space at the end of a right-justified line), and ATK will then adjust * the X and width values to positive to workaround RTL issues (see
* https://bugzilla.gnome.org/show_bug.cgi?id=102954), so we work around that */ if (loRect.Width < 0)
{ /* ATK will make x += width; width *= -1, but we don't really want to depend on the
* ATK behavior so we allow it to match as well */
CPPUNIT_ASSERT_MESSAGE(offsetMsg.getStr(),
loRect.X == sal_Int32(atspiRect.x)
|| loRect.X + loRect.Width == sal_Int32(atspiRect.x));
CPPUNIT_ASSERT_MESSAGE(offsetMsg.getStr(),
loRect.Width == sal_Int32(atspiRect.width)
|| -loRect.Width == sal_Int32(atspiRect.width));
} else
{ // normal case
CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), loRect.X, sal_Int32(atspiRect.x));
CPPUNIT_ASSERT_EQUAL_MESSAGE(offsetMsg.getStr(), loRect.Width,
sal_Int32(atspiRect.width));
}
// getRangeExtents() -- ATK doesn't like empty ranges, so only test when not empty if (characterCount > 0)
{ constauto loRangeBounds = getRangeBounds(xLOText, 0, characterCount); constauto atspiRangeExtents
= pAtspiText.getRangeExtents(0, characterCount, ATSPI_COORD_TYPE_WINDOW);
CPPUNIT_ASSERT_EQUAL(loRangeBounds.X, sal_Int32(atspiRangeExtents.x - atspiPosition.x));
CPPUNIT_ASSERT_EQUAL(loRangeBounds.Y, sal_Int32(atspiRangeExtents.y - atspiPosition.y));
CPPUNIT_ASSERT_EQUAL(loRangeBounds.Width, sal_Int32(atspiRangeExtents.width));
CPPUNIT_ASSERT_EQUAL(loRangeBounds.Height, sal_Int32(atspiRangeExtents.height));
}
// selection (LO only have one selection, so some of the API doesn't really make sense)
CPPUNIT_ASSERT_EQUAL(xLOText->getSelectionEnd() != xLOText->getSelectionStart() ? 1 : 0,
pAtspiText.getNSelections());
/* We need to take extra care with setSelection() because it could result to scrolling, which * might result in node destruction, which can mess up the parent's children enumeration. * So we only test nodes that are neither the first nor last child in its parent, hoping that
* means it won't require scrolling to show the end of the selection. */
uno::Reference<accessibility::XAccessibleContext> xLOContext(xLOText, uno::UNO_QUERY_THROW); constauto nIndexInParent = xLOContext->getAccessibleIndexInParent(); if (characterCount && nIndexInParent > 0
&& nIndexInParent + 1 < xLOContext->getAccessibleParent()
->getAccessibleContext()
->getAccessibleChildCount()
&& pAtspiText.setSelection(0, 0, characterCount))
{
CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xLOText->getSelectionStart());
CPPUNIT_ASSERT_EQUAL(characterCount, xLOText->getSelectionEnd()); // try and restore previous selection, if any
CPPUNIT_ASSERT(xLOText->setSelection(std::max(0, atspiSelection.startOffset),
std::max(0, atspiSelection.endOffset)));
}
// scrollSubstringTo() is tested in the parent, because it might dispose ourselves otherwise.
// TODO: more checks here...
}
#if HAVE_ATSPI2_SCROLL_TO // like getFirstRelationTargetOfType() but for Atspi objects static Atspi::Accessible
atspiGetFirstRelationTargetOfType(const Atspi::Accessible& pAtspiAccessible, const AtspiRelationType relationType)
{ for (constauto& rel : pAtspiAccessible.getRelationSet())
{ if (rel.getRelationType() == relationType && rel.getNTargets() > 0) return rel.getTarget(0);
}
return nullptr;
} #endif// HAVE_ATSPI2_SCROLL_TO
/** * @brief Gets the index of a Writer child hopping through flows-from relationships * @param xContext The accessible context to locate * @returns The index of @c xContext in the flows-from chain * * Gets the index of a child in its parent regardless of whether it is on screen or not. * * @warning This relying on the flows-from relationships, it only works for the connected nodes, * and might not work for e.g. frames.
*/
sal_Int64 Atspi2TestTree::swChildIndex(uno::Reference<accessibility::XAccessibleContext> xContext)
{ for (sal_Int64 n = 0;; n++)
{ auto xPrev = getFirstRelationTargetOfType(
xContext, accessibility::AccessibleRelationType_CONTENT_FLOWS_FROM); if (!xPrev.is()) return n;
xContext = xPrev;
}
}
/** * @brief tests scrolling in Writer. * @param xLOContext The @c XAccessibleContext for the writer document * @param xAtspiAccessible The AT-SPI2 equivalent of @c xLOContext. * * Test scrolling (currently XAccessibleText::scrollSubstringTo()) in Writer.
*/ void Atspi2TestTree::testSwScroll( const uno::Reference<accessibility::XAccessibleContext>& xLOContext, const Atspi::Accessible& xAtspiAccessible)
{ #if HAVE_ATSPI2_SCROLL_TO /* Currently LO only implements SCROLL_ANYWHERE, so to be sure we need to find something * offscreen and try and bring it in. LO only has implementation for SwAccessibleParagraph, * so we find the last child, and then try and find a FLOWS_TO relationship -- that's a hack * based on how LO exposes offscreen children, e.g. not as "real" children. Once done so, we * have to make sure the child is now on screen, so we should find it in the children list. We * cannot rely on anything we had still being visible, as it could very well have scrolled it to
* the top. */
assert(accessibility::AccessibleRole::DOCUMENT_TEXT == xLOContext->getAccessibleRole());
auto nLOChildCount = xLOContext->getAccessibleChildCount(); if (nLOChildCount <= 0) return;
// find the first off-screen text child auto xLONextContext = xLOContext->getAccessibleChild(nLOChildCount - 1)->getAccessibleContext();
uno::Reference<accessibility::XAccessibleText> xLONextText; unsignedint nAfterLast = 0; do
{
xLONextContext = getFirstRelationTargetOfType(
xLONextContext, accessibility::AccessibleRelationType_CONTENT_FLOWS_TO);
xLONextText.set(xLONextContext, uno::UNO_QUERY);
nAfterLast++;
} while (xLONextContext.is() && !xLONextText.is());
if (!xLONextText.is()) return; // we have nothing off-screen to scroll to
// get the global index of the off-screen child so we can match it later auto nLOChildIndex = swChildIndex(xLONextContext);
// find the corresponding Atspi child to call the API on auto xAtspiNextChild = xAtspiAccessible.getChildAtIndex(nLOChildCount - 1); while (nAfterLast-- > 0 && xAtspiNextChild)
xAtspiNextChild
= atspiGetFirstRelationTargetOfType(xAtspiNextChild, ATSPI_RELATION_FLOWS_TO); /* the child ought to be found and implement the same interfaces, otherwise there's a problem
* in LO <> Atspi child mapping */
CPPUNIT_ASSERT(xAtspiNextChild); constauto xAtspiNextText = xAtspiNextChild.queryText();
// scroll the child into view
CPPUNIT_ASSERT(xAtspiNextText.scrollSubstringTo(0, 1, ATSPI_SCROLL_ANYWHERE));
// now, check that the nLOChildIndex is in the visible area (among the regular children)
nLOChildCount = xLOContext->getAccessibleChildCount();
CPPUNIT_ASSERT_GREATER(sal_Int64(0), nLOChildCount); constauto nLOFirstChildIndex
= swChildIndex(xLOContext->getAccessibleChild(0)->getAccessibleContext());
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.