diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sw/source/core/access | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/access')
53 files changed, 20516 insertions, 0 deletions
diff --git a/sw/source/core/access/AccessibilityCheck.cxx b/sw/source/core/access/AccessibilityCheck.cxx new file mode 100644 index 000000000..50a37dc00 --- /dev/null +++ b/sw/source/core/access/AccessibilityCheck.cxx @@ -0,0 +1,969 @@ +/* -*- 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/. + * + */ + +#include <AccessibilityCheck.hxx> +#include <AccessibilityIssue.hxx> +#include <AccessibilityCheckStrings.hrc> +#include <ndnotxt.hxx> +#include <ndtxt.hxx> +#include <docsh.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <drawdoc.hxx> +#include <svx/svdpage.hxx> +#include <swtable.hxx> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <unoparagraph.hxx> +#include <tools/urlobj.hxx> +#include <editeng/langitem.hxx> +#include <charatr.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <ftnidx.hxx> +#include <txtftn.hxx> +#include <svl/itemiter.hxx> +#include <o3tl/vector_utils.hxx> +#include <svx/swframetypes.hxx> +#include <fmtanchr.hxx> +#include <dcontact.hxx> +#include <svx/svdoashp.hxx> +#include <svx/sdasitm.hxx> + +namespace sw +{ +namespace +{ +std::shared_ptr<sw::AccessibilityIssue> +lclAddIssue(sfx::AccessibilityIssueCollection& rIssueCollection, OUString const& rText, + sfx::AccessibilityIssueID eIssue = sfx::AccessibilityIssueID::UNSPECIFIED) +{ + auto pIssue = std::make_shared<sw::AccessibilityIssue>(eIssue); + pIssue->m_aIssueText = rText; + rIssueCollection.getIssues().push_back(pIssue); + return pIssue; +} + +class BaseCheck +{ +protected: + sfx::AccessibilityIssueCollection& m_rIssueCollection; + +public: + BaseCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : m_rIssueCollection(rIssueCollection) + { + } + virtual ~BaseCheck() {} +}; + +class NodeCheck : public BaseCheck +{ +public: + NodeCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : BaseCheck(rIssueCollection) + { + } + + virtual void check(SwNode* pCurrent) = 0; +}; + +// Check NoTextNodes: Graphic, OLE for alt (title) text +class NoTextNodeAltTextCheck : public NodeCheck +{ + void checkNoTextNode(SwNoTextNode* pNoTextNode) + { + if (!pNoTextNode) + return; + + OUString sAlternative = pNoTextNode->GetTitle(); + if (!sAlternative.isEmpty()) + return; + + OUString sName = pNoTextNode->GetFlyFormat()->GetName(); + + OUString sIssueText = SwResId(STR_NO_ALT).replaceAll("%OBJECT_NAME%", sName); + + if (pNoTextNode->IsOLENode()) + { + auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText, + sfx::AccessibilityIssueID::NO_ALT_OLE); + pIssue->setDoc(pNoTextNode->GetDoc()); + pIssue->setIssueObject(IssueObject::OLE); + pIssue->setObjectID(pNoTextNode->GetFlyFormat()->GetName()); + } + else if (pNoTextNode->IsGrfNode()) + { + auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText, + sfx::AccessibilityIssueID::NO_ALT_GRAPHIC); + pIssue->setDoc(pNoTextNode->GetDoc()); + pIssue->setIssueObject(IssueObject::GRAPHIC); + pIssue->setObjectID(pNoTextNode->GetFlyFormat()->GetName()); + } + } + +public: + NoTextNodeAltTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (pCurrent->GetNodeType() & SwNodeType::NoTextMask) + { + SwNoTextNode* pNoTextNode = pCurrent->GetNoTextNode(); + if (pNoTextNode) + checkNoTextNode(pNoTextNode); + } + } +}; + +// Check Table node if the table is merged and split. +class TableNodeMergeSplitCheck : public NodeCheck +{ +private: + void addTableIssue(SwTable const& rTable, SwDoc& rDoc) + { + const SwTableFormat* pFormat = rTable.GetFrameFormat(); + OUString sName = pFormat->GetName(); + OUString sIssueText = SwResId(STR_TABLE_MERGE_SPLIT).replaceAll("%OBJECT_NAME%", sName); + auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText, + sfx::AccessibilityIssueID::TABLE_MERGE_SPLIT); + pIssue->setDoc(rDoc); + pIssue->setIssueObject(IssueObject::TABLE); + pIssue->setObjectID(sName); + } + + void checkTableNode(SwTableNode* pTableNode) + { + if (!pTableNode) + return; + + SwTable const& rTable = pTableNode->GetTable(); + SwDoc& rDoc = pTableNode->GetDoc(); + if (rTable.IsTableComplex()) + { + addTableIssue(rTable, rDoc); + } + else + { + if (rTable.GetTabLines().size() > 1) + { + int i = 0; + size_t nFirstLineSize = 0; + bool bAllColumnsSameSize = true; + bool bCellSpansOverMoreRows = false; + + for (SwTableLine const* pTableLine : rTable.GetTabLines()) + { + if (i == 0) + { + nFirstLineSize = pTableLine->GetTabBoxes().size(); + } + else + { + size_t nLineSize = pTableLine->GetTabBoxes().size(); + if (nFirstLineSize != nLineSize) + { + bAllColumnsSameSize = false; + } + } + i++; + + // Check for row span in each table box (cell) + for (SwTableBox const* pBox : pTableLine->GetTabBoxes()) + { + if (pBox->getRowSpan() > 1) + bCellSpansOverMoreRows = true; + } + } + if (!bAllColumnsSameSize || bCellSpansOverMoreRows) + { + addTableIssue(rTable, rDoc); + } + } + } + } + +public: + TableNodeMergeSplitCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (pCurrent->GetNodeType() & SwNodeType::Table) + { + SwTableNode* pTableNode = pCurrent->GetTableNode(); + if (pTableNode) + checkTableNode(pTableNode); + } + } +}; + +class NumberingCheck : public NodeCheck +{ +private: + SwTextNode* m_pPreviousTextNode; + + const std::vector<std::pair<OUString, OUString>> m_aNumberingCombinations{ + { "1.", "2." }, { "(1)", "(2)" }, { "1)", "2)" }, { "a.", "b." }, { "(a)", "(b)" }, + { "a)", "b)" }, { "A.", "B." }, { "(A)", "(B)" }, { "A)", "B)" } + }; + +public: + NumberingCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + , m_pPreviousTextNode(nullptr) + { + } + + void check(SwNode* pCurrent) override + { + if (!pCurrent->IsTextNode()) + return; + + if (m_pPreviousTextNode) + { + for (auto& rPair : m_aNumberingCombinations) + { + if (pCurrent->GetTextNode()->GetText().startsWith(rPair.second) + && m_pPreviousTextNode->GetText().startsWith(rPair.first)) + { + OUString sNumbering = rPair.first + " " + rPair.second + "..."; + OUString sIssueText + = SwResId(STR_FAKE_NUMBERING).replaceAll("%NUMBERING%", sNumbering); + lclAddIssue(m_rIssueCollection, sIssueText); + } + } + } + m_pPreviousTextNode = pCurrent->GetTextNode(); + } +}; + +class HyperlinkCheck : public NodeCheck +{ +private: + void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange) + { + uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY); + if (!xProperties->getPropertySetInfo()->hasPropertyByName("HyperLinkURL")) + return; + + OUString sHyperlink; + xProperties->getPropertyValue("HyperLinkURL") >>= sHyperlink; + if (!sHyperlink.isEmpty()) + { + OUString sText = xTextRange->getString(); + if (INetURLObject(sText) == INetURLObject(sHyperlink)) + { + OUString sIssueText + = SwResId(STR_HYPERLINK_TEXT_IS_LINK).replaceFirst("%LINK%", sHyperlink); + lclAddIssue(m_rIssueCollection, sIssueText); + } + } + } + +public: + HyperlinkCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (!pCurrent->IsTextNode()) + return; + + SwTextNode* pTextNode = pCurrent->GetTextNode(); + uno::Reference<text::XTextContent> xParagraph + = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode); + if (!xParagraph.is()) + return; + + uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration(); + while (xRunEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY); + if (xRun.is()) + { + checkTextRange(xRun); + } + } + } +}; + +// Based on https://www.w3.org/TR/WCAG21/#dfn-relative-luminance +double calculateRelativeLuminance(Color const& rColor) +{ + // Convert to BColor which has R, G, B colors components + // represented by a floating point number from [0.0, 1.0] + const basegfx::BColor aBColor = rColor.getBColor(); + + double r = aBColor.getRed(); + double g = aBColor.getGreen(); + double b = aBColor.getBlue(); + + // Calculate the values according to the described algorithm + r = (r <= 0.03928) ? r / 12.92 : std::pow((r + 0.055) / 1.055, 2.4); + g = (g <= 0.03928) ? g / 12.92 : std::pow((g + 0.055) / 1.055, 2.4); + b = (b <= 0.03928) ? b / 12.92 : std::pow((b + 0.055) / 1.055, 2.4); + + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +} + +// TODO move to common color tools (BColorTools maybe) +// Based on https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio +double calculateContrastRatio(Color const& rColor1, Color const& rColor2) +{ + const double fLuminance1 = calculateRelativeLuminance(rColor1); + const double fLuminance2 = calculateRelativeLuminance(rColor2); + const std::pair<const double, const double> aMinMax = std::minmax(fLuminance1, fLuminance2); + + // (L1 + 0.05) / (L2 + 0.05) + // L1 is the lighter color (greater luminance value) + // L2 is the darker color (smaller luminance value) + return (aMinMax.second + 0.05) / (aMinMax.first + 0.05); +} + +class TextContrastCheck : public NodeCheck +{ +private: + void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange, + uno::Reference<text::XTextContent> const& xParagraph, + const SwTextNode* pTextNode) + { + Color nParaBackColor(COL_AUTO); + uno::Reference<beans::XPropertySet> xParagraphProperties(xParagraph, uno::UNO_QUERY); + if (!(xParagraphProperties->getPropertyValue("ParaBackColor") >>= nParaBackColor)) + { + SAL_WARN("sw.a11y", "ParaBackColor void"); + return; + } + + uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY); + if (!xProperties.is()) + return; + + // Foreground color + sal_Int32 nCharColor = {}; // spurious -Werror=maybe-uninitialized + if (!(xProperties->getPropertyValue("CharColor") >>= nCharColor)) + { // not sure this is impossible, can the default be void? + SAL_WARN("sw.a11y", "CharColor void"); + return; + } + Color aForegroundColor(ColorTransparency, nCharColor); + if (aForegroundColor == COL_AUTO) + return; + + const SwPageDesc* pPageDescription = pTextNode->FindPageDesc(); + const SwFrameFormat& rPageFormat = pPageDescription->GetMaster(); + const SwAttrSet& rPageSet = rPageFormat.GetAttrSet(); + + const XFillStyleItem* pXFillStyleItem( + rPageSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE, false)); + Color aPageBackground(COL_AUTO); + + if (pXFillStyleItem && pXFillStyleItem->GetValue() == css::drawing::FillStyle_SOLID) + { + const XFillColorItem* rXFillColorItem + = rPageSet.GetItem<XFillColorItem>(XATTR_FILLCOLOR, false); + aPageBackground = rXFillColorItem->GetColorValue(); + } + + Color nCharBackColor(COL_AUTO); + + if (!(xProperties->getPropertyValue("CharBackColor") >>= nCharBackColor)) + { + SAL_WARN("sw.a11y", "CharBackColor void"); + return; + } + // Determine the background color + // Try Character background (highlight) + Color aBackgroundColor(nCharBackColor); + + // If not character background color, try paragraph background color + if (aBackgroundColor == COL_AUTO) + aBackgroundColor = nParaBackColor; + + // If not paragraph background color, try page color + if (aBackgroundColor == COL_AUTO) + aBackgroundColor = aPageBackground; + + // If not page color, assume white background color + if (aBackgroundColor == COL_AUTO) + aBackgroundColor = COL_WHITE; + + double fContrastRatio = calculateContrastRatio(aForegroundColor, aBackgroundColor); + if (fContrastRatio < 4.5) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_CONTRAST)); + } + } + +public: + TextContrastCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (!pCurrent->IsTextNode()) + return; + + SwTextNode* pTextNode = pCurrent->GetTextNode(); + uno::Reference<text::XTextContent> xParagraph; + xParagraph = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode); + if (!xParagraph.is()) + return; + + uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration(); + while (xRunEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY); + if (xRun.is()) + checkTextRange(xRun, xParagraph, pTextNode); + } + } +}; + +class TextFormattingCheck : public NodeCheck +{ +private: +public: + TextFormattingCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void checkAutoFormat(SwTextNode* pTextNode, const SwTextAttr* pTextAttr) + { + const SwFormatAutoFormat& rAutoFormat = pTextAttr->GetAutoFormat(); + SfxItemIter aItemIter(*rAutoFormat.GetStyleHandle()); + const SfxPoolItem* pItem = aItemIter.GetCurItem(); + std::vector<OUString> aFormattings; + while (pItem) + { + OUString sFormattingType; + switch (pItem->Which()) + { + case RES_CHRATR_WEIGHT: + case RES_CHRATR_CJK_WEIGHT: + case RES_CHRATR_CTL_WEIGHT: + sFormattingType = "Weight"; + break; + case RES_CHRATR_POSTURE: + case RES_CHRATR_CJK_POSTURE: + case RES_CHRATR_CTL_POSTURE: + sFormattingType = "Posture"; + break; + + case RES_CHRATR_SHADOWED: + sFormattingType = "Shadowed"; + break; + + case RES_CHRATR_COLOR: + sFormattingType = "Font Color"; + break; + + case RES_CHRATR_FONTSIZE: + case RES_CHRATR_CJK_FONTSIZE: + case RES_CHRATR_CTL_FONTSIZE: + sFormattingType = "Font Size"; + break; + + case RES_CHRATR_FONT: + case RES_CHRATR_CJK_FONT: + case RES_CHRATR_CTL_FONT: + sFormattingType = "Font"; + break; + + case RES_CHRATR_EMPHASIS_MARK: + sFormattingType = "Emphasis Mark"; + break; + + case RES_CHRATR_UNDERLINE: + sFormattingType = "Underline"; + break; + + case RES_CHRATR_OVERLINE: + sFormattingType = "Overline"; + break; + + case RES_CHRATR_CROSSEDOUT: + sFormattingType = "Strikethrough"; + break; + + case RES_CHRATR_RELIEF: + sFormattingType = "Relief"; + break; + + case RES_CHRATR_CONTOUR: + sFormattingType = "Outline"; + break; + default: + break; + } + if (!sFormattingType.isEmpty()) + aFormattings.push_back(sFormattingType); + pItem = aItemIter.NextItem(); + } + if (aFormattings.empty()) + return; + + o3tl::remove_duplicates(aFormattings); + auto pIssue = lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_FORMATTING_CONVEYS_MEANING), + sfx::AccessibilityIssueID::TEXT_FORMATTING); + pIssue->setIssueObject(IssueObject::TEXT); + pIssue->setNode(pTextNode); + SwDoc& rDocument = pTextNode->GetDoc(); + pIssue->setDoc(rDocument); + pIssue->setStart(pTextAttr->GetStart()); + pIssue->setEnd(pTextAttr->GetAnyEnd()); + } + void check(SwNode* pCurrent) override + { + if (!pCurrent->IsTextNode()) + return; + + SwTextNode* pTextNode = pCurrent->GetTextNode(); + if (pTextNode->HasHints()) + { + SwpHints& rHints = pTextNode->GetSwpHints(); + for (size_t i = 0; i < rHints.Count(); ++i) + { + const SwTextAttr* pTextAttr = rHints.Get(i); + if (pTextAttr->Which() == RES_TXTATR_AUTOFMT) + { + checkAutoFormat(pTextNode, pTextAttr); + } + } + } + } +}; + +class BlinkingTextCheck : public NodeCheck +{ +private: + void checkTextRange(uno::Reference<text::XTextRange> const& xTextRange) + { + uno::Reference<beans::XPropertySet> xProperties(xTextRange, uno::UNO_QUERY); + if (xProperties.is() && xProperties->getPropertySetInfo()->hasPropertyByName("CharFlash")) + { + bool bBlinking = false; + xProperties->getPropertyValue("CharFlash") >>= bBlinking; + + if (bBlinking) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_BLINKING)); + } + } + } + +public: + BlinkingTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (!pCurrent->IsTextNode()) + return; + + SwTextNode* pTextNode = pCurrent->GetTextNode(); + uno::Reference<text::XTextContent> xParagraph; + xParagraph = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode); + if (!xParagraph.is()) + return; + + uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParagraph, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration(); + while (xRunEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY); + if (xRun.is()) + checkTextRange(xRun); + } + } +}; + +class HeaderCheck : public NodeCheck +{ +private: + int m_nPreviousLevel; + +public: + HeaderCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + , m_nPreviousLevel(0) + { + } + + void check(SwNode* pCurrent) override + { + if (!pCurrent->IsTextNode()) + return; + + SwTextNode* pTextNode = pCurrent->GetTextNode(); + SwTextFormatColl* pCollection = pTextNode->GetTextColl(); + if (!pCollection->IsAssignedToListLevelOfOutlineStyle()) + return; + + int nLevel = pCollection->GetAssignedOutlineStyleLevel(); + assert(nLevel >= 0); + if (nLevel > m_nPreviousLevel && std::abs(nLevel - m_nPreviousLevel) > 1) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_HEADINGS_NOT_IN_ORDER)); + } + m_nPreviousLevel = nLevel; + } +}; + +// ISO 142891-1 : 7.14 +class NonInteractiveFormCheck : public NodeCheck +{ +public: + NonInteractiveFormCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (!pCurrent->IsTextNode()) + return; + + const auto& text = pCurrent->GetTextNode()->GetText(); + + // Series of tests to detect if there are fake forms in the text. + + bool bCheck = text.indexOf("___") == -1; // Repeated underscores. + + if (bCheck) + bCheck = text.indexOf("....") == -1; // Repeated dots. + + if (bCheck) + bCheck = text.indexOf(u"……") == -1; // Repeated ellipsis. + + if (bCheck) + bCheck = text.indexOf(u"….") == -1; // A dot after an ellipsis. + + if (bCheck) + bCheck = text.indexOf(u".…") == -1; // An ellipsis after a dot. + + // Checking if all the tests are passed successfully. If not, adding a warning. + if (!bCheck) + lclAddIssue(m_rIssueCollection, SwResId(STR_NON_INTERACTIVE_FORMS)); + } +}; + +/// Check for floating text frames, as it causes problems with reading order. +class FloatingTextCheck : public NodeCheck +{ +public: + FloatingTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + // if node is a text-node and if it has text, we proceed. Otherwise - return. + const SwTextNode* textNode = pCurrent->GetTextNode(); + if (!textNode || textNode->GetText().isEmpty()) + return; + + // If a node is in fly and if it is not anchored as char, throw warning. + const SwNode* startFly = pCurrent->FindFlyStartNode(); + if (startFly + && startFly->GetFlyFormat()->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + lclAddIssue(m_rIssueCollection, SwResId(STR_FLOATING_TEXT)); + } +}; + +/// Heading paragraphs (with outline levels > 0) are not allowed in tables +class TableHeadingCheck : public NodeCheck +{ +private: + // Boolean indicating if heading-in-table warning is already triggered. + bool m_bPrevPassed; + +public: + TableHeadingCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + , m_bPrevPassed(true) + { + } + + void check(SwNode* pCurrent) override + { + if (!m_bPrevPassed) + return; + + const SwTextNode* textNode = pCurrent->GetTextNode(); + + if (textNode && textNode->GetAttrOutlineLevel() != 0) + { + const SwTableNode* parentTable = pCurrent->FindTableNode(); + + if (parentTable) + { + m_bPrevPassed = false; + lclAddIssue(m_rIssueCollection, SwResId(STR_HEADING_IN_TABLE)); + } + } + } +}; + +/// Checking if headings are ordered correctly. +class HeadingOrderCheck : public NodeCheck +{ +public: + HeadingOrderCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + const SwTextNode* pTextNode = pCurrent->GetTextNode(); + if (!pTextNode) + return; + + // If outline level stands for heading level... + const int currentLevel = pTextNode->GetAttrOutlineLevel(); + if (!currentLevel) + return; + + // ... and if is bigger than previous by more than 1, warn. + if (currentLevel - m_prevLevel > 1) + { + // Preparing and posting a warning. + OUString resultString = SwResId(STR_HEADING_ORDER); + resultString + = resultString.replaceAll("%LEVEL_CURRENT%", OUString::number(currentLevel)); + resultString = resultString.replaceAll("%LEVEL_PREV%", OUString::number(m_prevLevel)); + + lclAddIssue(m_rIssueCollection, resultString); + } + + // Updating previous level. + m_prevLevel = currentLevel; + } + +private: + // Previous heading level to compare with. + int m_prevLevel = 0; +}; + +class DocumentCheck : public BaseCheck +{ +public: + DocumentCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : BaseCheck(rIssueCollection) + { + } + + virtual void check(SwDoc* pDoc) = 0; +}; + +// Check default language +class DocumentDefaultLanguageCheck : public DocumentCheck +{ +public: + DocumentDefaultLanguageCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : DocumentCheck(rIssueCollection) + { + } + + void check(SwDoc* pDoc) override + { + // TODO maybe - also check RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CTL_LANGUAGE if CJK or CTL are enabled + const SvxLanguageItem& rLang = pDoc->GetDefault(RES_CHRATR_LANGUAGE); + LanguageType eLanguage = rLang.GetLanguage(); + if (eLanguage == LANGUAGE_NONE) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_DOCUMENT_DEFAULT_LANGUAGE), + sfx::AccessibilityIssueID::DOCUMENT_LANGUAGE); + } + else + { + for (SwTextFormatColl* pTextFormatCollection : *pDoc->GetTextFormatColls()) + { + const SwAttrSet& rAttrSet = pTextFormatCollection->GetAttrSet(); + if (rAttrSet.GetLanguage(false).GetLanguage() == LANGUAGE_NONE) + { + OUString sName = pTextFormatCollection->GetName(); + OUString sIssueText + = SwResId(STR_STYLE_NO_LANGUAGE).replaceAll("%STYLE_NAME%", sName); + lclAddIssue(m_rIssueCollection, sIssueText, + sfx::AccessibilityIssueID::STYLE_LANGUAGE); + } + } + } + } +}; + +class DocumentTitleCheck : public DocumentCheck +{ +public: + DocumentTitleCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : DocumentCheck(rIssueCollection) + { + } + + void check(SwDoc* pDoc) override + { + SwDocShell* pShell = pDoc->GetDocShell(); + if (!pShell) + return; + + const uno::Reference<document::XDocumentPropertiesSupplier> xDPS(pShell->GetModel(), + uno::UNO_QUERY_THROW); + const uno::Reference<document::XDocumentProperties> xDocumentProperties( + xDPS->getDocumentProperties()); + OUString sTitle = xDocumentProperties->getTitle(); + if (sTitle.trim().isEmpty()) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_DOCUMENT_TITLE), + sfx::AccessibilityIssueID::DOCUMENT_TITLE); + } + } +}; + +class FootnoteEndnoteCheck : public DocumentCheck +{ +public: + FootnoteEndnoteCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : DocumentCheck(rIssueCollection) + { + } + + void check(SwDoc* pDoc) override + { + for (SwTextFootnote const* pTextFootnote : pDoc->GetFootnoteIdxs()) + { + SwFormatFootnote const& rFootnote = pTextFootnote->GetFootnote(); + if (rFootnote.IsEndNote()) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_ENDNOTES)); + } + else + { + lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_FOOTNOTES)); + } + } + } +}; + +} // end anonymous namespace + +// Check Shapes, TextBox +void AccessibilityCheck::checkObject(SdrObject* pObject) +{ + if (!pObject) + return; + + // Check for fontworks. + if (SdrObjCustomShape* pCustomShape = dynamic_cast<SdrObjCustomShape*>(pObject)) + { + const SdrCustomShapeGeometryItem& rGeometryItem + = pCustomShape->GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY); + + if (const uno::Any* pAny = rGeometryItem.GetPropertyValueByName("Type")) + if (pAny->get<OUString>().startsWith("fontwork-")) + lclAddIssue(m_aIssueCollection, SwResId(STR_FONTWORKS)); + } + + // Checking if there is floating Writer text draw object and if so, throwing a warning. + // (Floating objects with text create problems with reading order) + if (pObject->HasText() + && FindFrameFormat(pObject)->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + lclAddIssue(m_aIssueCollection, SwResId(STR_FLOATING_TEXT)); + + if (pObject->GetObjIdentifier() == SdrObjKind::CustomShape + || pObject->GetObjIdentifier() == SdrObjKind::Text) + { + OUString sAlternative = pObject->GetTitle(); + if (sAlternative.isEmpty()) + { + OUString sName = pObject->GetName(); + OUString sIssueText = SwResId(STR_NO_ALT).replaceAll("%OBJECT_NAME%", sName); + lclAddIssue(m_aIssueCollection, sIssueText, sfx::AccessibilityIssueID::NO_ALT_SHAPE); + } + } +} + +void AccessibilityCheck::check() +{ + if (m_pDoc == nullptr) + return; + + std::vector<std::unique_ptr<DocumentCheck>> aDocumentChecks; + aDocumentChecks.push_back(std::make_unique<DocumentDefaultLanguageCheck>(m_aIssueCollection)); + aDocumentChecks.push_back(std::make_unique<DocumentTitleCheck>(m_aIssueCollection)); + aDocumentChecks.push_back(std::make_unique<FootnoteEndnoteCheck>(m_aIssueCollection)); + + for (std::unique_ptr<DocumentCheck>& rpDocumentCheck : aDocumentChecks) + { + rpDocumentCheck->check(m_pDoc); + } + + std::vector<std::unique_ptr<NodeCheck>> aNodeChecks; + aNodeChecks.push_back(std::make_unique<NoTextNodeAltTextCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<TableNodeMergeSplitCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<NumberingCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<HyperlinkCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<TextContrastCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<BlinkingTextCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<HeaderCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<TextFormattingCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<NonInteractiveFormCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<FloatingTextCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<TableHeadingCheck>(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique<HeadingOrderCheck>(m_aIssueCollection)); + + auto const& pNodes = m_pDoc->GetNodes(); + SwNode* pNode = nullptr; + for (SwNodeOffset n(0); n < pNodes.Count(); ++n) + { + pNode = pNodes[n]; + if (pNode) + { + for (std::unique_ptr<NodeCheck>& rpNodeCheck : aNodeChecks) + { + rpNodeCheck->check(pNode); + } + } + } + + IDocumentDrawModelAccess& rDrawModelAccess = m_pDoc->getIDocumentDrawModelAccess(); + auto* pModel = rDrawModelAccess.GetDrawModel(); + for (sal_uInt16 nPage = 0; nPage < pModel->GetPageCount(); ++nPage) + { + SdrPage* pPage = pModel->GetPage(nPage); + for (size_t nObject = 0; nObject < pPage->GetObjCount(); ++nObject) + { + SdrObject* pObject = pPage->GetObj(nObject); + if (pObject) + checkObject(pObject); + } + } +} + +} // end sw namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/AccessibilityIssue.cxx b/sw/source/core/access/AccessibilityIssue.cxx new file mode 100644 index 000000000..f8b8e3858 --- /dev/null +++ b/sw/source/core/access/AccessibilityIssue.cxx @@ -0,0 +1,89 @@ +/* -*- 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/. + * + */ + +#include <AccessibilityIssue.hxx> +#include <wrtsh.hxx> +#include <docsh.hxx> +#include <comphelper/lok.hxx> + +namespace sw +{ +AccessibilityIssue::AccessibilityIssue(sfx::AccessibilityIssueID eIssueID) + : sfx::AccessibilityIssue(eIssueID) + , m_eIssueObject(IssueObject::UNKNOWN) + , m_pDoc(nullptr) + , m_pNode(nullptr) + , m_nStart(0) + , m_nEnd(0) +{ +} + +void AccessibilityIssue::setIssueObject(IssueObject eIssueObject) { m_eIssueObject = eIssueObject; } + +void AccessibilityIssue::setDoc(SwDoc& rDoc) { m_pDoc = &rDoc; } + +void AccessibilityIssue::setObjectID(OUString const& rID) { m_sObjectID = rID; } + +bool AccessibilityIssue::canGotoIssue() const +{ + if (m_eIssueObject != IssueObject::UNKNOWN) + return true; + return false; +} + +void AccessibilityIssue::gotoIssue() const +{ + if (!m_pDoc) + return; + + switch (m_eIssueObject) + { + case IssueObject::GRAPHIC: + case IssueObject::OLE: + { + SwWrtShell* pWrtShell = m_pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->GotoFly(m_sObjectID, FLYCNTTYPE_ALL, true); + if (comphelper::LibreOfficeKit::isActive()) + pWrtShell->ShowCursor(); + } + break; + case IssueObject::TABLE: + { + SwWrtShell* pWrtShell = m_pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->GotoTable(m_sObjectID); + if (comphelper::LibreOfficeKit::isActive()) + pWrtShell->ShowCursor(); + } + break; + case IssueObject::TEXT: + { + SwWrtShell* pWrtShell = m_pDoc->GetDocShell()->GetWrtShell(); + SwContentNode* pContentNode = m_pNode->GetContentNode(); + SwPosition aPoint(*pContentNode, m_nStart); + SwPosition aMark(*pContentNode, m_nEnd); + pWrtShell->EnterStdMode(); + pWrtShell->StartAllAction(); + SwPaM* pPaM = pWrtShell->GetCursor(); + *pPaM->GetPoint() = aPoint; + pPaM->SetMark(); + *pPaM->GetMark() = aMark; + pWrtShell->EndAllAction(); + if (comphelper::LibreOfficeKit::isActive()) + pWrtShell->ShowCursor(); + } + break; + default: + break; + } +} + +} // end sw namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acccell.cxx b/sw/source/core/access/acccell.cxx new file mode 100644 index 000000000..546c0457c --- /dev/null +++ b/sw/source/core/access/acccell.cxx @@ -0,0 +1,472 @@ +/* -*- 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 . + */ + +#include <sal/log.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <unotools/accessiblestatesethelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <vcl/svapp.hxx> +#include <cellfrm.hxx> +#include <tabfrm.hxx> +#include <swtable.hxx> +#include <crsrsh.hxx> +#include <viscrs.hxx> +#include "accfrmobj.hxx" +#include "accfrmobjslist.hxx" +#include <frmfmt.hxx> +#include <cellatr.hxx> +#include <accmap.hxx> +#include "acccell.hxx" + +#include <cfloat> +#include <string_view> + +#include <editeng/brushitem.hxx> +#include <swatrset.hxx> +#include <frmatr.hxx> +#include "acctable.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace sw::access; + +constexpr OUStringLiteral sImplementationName = u"com.sun.star.comp.Writer.SwAccessibleCellView"; + +bool SwAccessibleCell::IsSelected() +{ + bool bRet = false; + + assert(GetMap()); + const SwViewShell *pVSh = GetMap()->GetShell(); + assert(pVSh); + if( auto pCSh = dynamic_cast<const SwCursorShell*>(pVSh) ) + { + if( pCSh->IsTableMode() ) + { + const SwCellFrame *pCFrame = + static_cast< const SwCellFrame * >( GetFrame() ); + SwTableBox *pBox = + const_cast< SwTableBox *>( pCFrame->GetTabBox() ); + SwSelBoxes const& rBoxes(pCSh->GetTableCursor()->GetSelectedBoxes()); + bRet = rBoxes.find(pBox) != rBoxes.end(); + } + } + + return bRet; +} + +void SwAccessibleCell::GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + // SELECTABLE + const SwViewShell *pVSh = GetMap()->GetShell(); + assert(pVSh); + if( dynamic_cast<const SwCursorShell*>( pVSh) != nullptr ) + rStateSet.AddState( AccessibleStateType::SELECTABLE ); + //Add resizable state to table cell. + rStateSet.AddState( AccessibleStateType::RESIZABLE ); + + if (IsDisposing()) // tdf#135098 + return; + + // SELECTED + if( IsSelected() ) + { + rStateSet.AddState( AccessibleStateType::SELECTED ); + SAL_WARN_IF(!m_bIsSelected, "sw.a11y", "bSelected out of sync"); + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } +} + +SwAccessibleCell::SwAccessibleCell(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwCellFrame *pCellFrame ) + : SwAccessibleContext( pInitMap, AccessibleRole::TABLE_CELL, pCellFrame ) + , m_aSelectionHelper( *this ) + , m_bIsSelected( false ) +{ + OUString sBoxName( pCellFrame->GetTabBox()->GetName() ); + SetName( sBoxName ); + + m_bIsSelected = IsSelected(); + + css::uno::Reference<css::accessibility::XAccessible> xTableReference( + getAccessibleParentImpl()); + css::uno::Reference<css::accessibility::XAccessibleContext> xContextTable( + xTableReference, css::uno::UNO_QUERY); + SAL_WARN_IF( + (!xContextTable.is() + || xContextTable->getAccessibleRole() != AccessibleRole::TABLE), + "sw.a11y", "bad accessible context"); + m_pAccTable = static_cast<SwAccessibleTable *>(xTableReference.get()); +} + +bool SwAccessibleCell::InvalidateMyCursorPos() +{ + bool bNew = IsSelected(); + bool bOld; + { + std::scoped_lock aGuard( m_Mutex ); + bOld = m_bIsSelected; + m_bIsSelected = bNew; + } + if( bNew ) + { + // remember that object as the one that has the caret. This is + // necessary to notify that object if the cursor leaves it. + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + bool bChanged = bOld != bNew; + if( bChanged ) + { + FireStateChangedEvent( AccessibleStateType::SELECTED, bNew ); + if (m_pAccTable.is()) + { + m_pAccTable->AddSelectionCell(this,bNew); + } + } + return bChanged; +} + +bool SwAccessibleCell::InvalidateChildrenCursorPos( const SwFrame *pFrame ) +{ + bool bChanged = false; + + const SwAccessibleChildSList aVisList( GetVisArea(), *pFrame, *GetMap() ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame *pLower = rLower.GetSwFrame(); + if( pLower ) + { + if( rLower.IsAccessible( GetMap()->GetShell()->IsPreview() ) ) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl( + GetMap()->GetContextImpl( pLower, false ) ); + if( xAccImpl.is() ) + { + assert(xAccImpl->GetFrame()->IsCellFrame()); + bChanged = static_cast< SwAccessibleCell *>( + xAccImpl.get() )->InvalidateMyCursorPos(); + } + else + bChanged = true; // If the context is not know we + // don't know whether the selection + // changed or not. + } + else + { + // This is a box with sub rows. + bChanged |= InvalidateChildrenCursorPos( pLower ); + } + } + ++aIter; + } + + return bChanged; +} + +void SwAccessibleCell::InvalidateCursorPos_() +{ + if (IsSelected()) + { + const SwAccessibleChild aChild( GetChild( *(GetMap()), 0 ) ); + if( aChild.IsValid() && aChild.GetSwFrame() ) + { + ::rtl::Reference < SwAccessibleContext > xChildImpl( GetMap()->GetContextImpl( aChild.GetSwFrame()) ); + if (xChildImpl.is()) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::STATE_CHANGED; + aEvent.NewValue <<= AccessibleStateType::FOCUSED; + xChildImpl->FireAccessibleEvent( aEvent ); + } + } + } + + const SwFrame *pParent = GetParent( SwAccessibleChild(GetFrame()), IsInPagePreview() ); + assert(pParent->IsTabFrame()); + const SwTabFrame *pTabFrame = static_cast< const SwTabFrame * >( pParent ); + if( pTabFrame->IsFollow() ) + pTabFrame = pTabFrame->FindMaster(); + + while( pTabFrame ) + { + InvalidateChildrenCursorPos( pTabFrame ); + pTabFrame = pTabFrame->GetFollow(); + } + if (m_pAccTable.is()) + { + m_pAccTable->FireSelectionEvent(); + } +} + +bool SwAccessibleCell::HasCursor() +{ + std::scoped_lock aGuard( m_Mutex ); + return m_bIsSelected; +} + +SwAccessibleCell::~SwAccessibleCell() +{ +} + +OUString SAL_CALL SwAccessibleCell::getAccessibleDescription() +{ + return GetName(); +} + +OUString SAL_CALL SwAccessibleCell::getImplementationName() +{ + return sImplementationName; +} + +sal_Bool SAL_CALL SwAccessibleCell::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleCell::getSupportedServiceNames() +{ + return { "com.sun.star.table.AccessibleCellView", sAccessibleServiceName }; +} + +void SwAccessibleCell::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + const SwFrame *pParent = GetParent( SwAccessibleChild(GetFrame()), IsInPagePreview() ); + ::rtl::Reference< SwAccessibleContext > xAccImpl( + GetMap()->GetContextImpl( pParent, false ) ); + if( xAccImpl.is() ) + xAccImpl->DisposeChild(SwAccessibleChild(GetFrame()), bRecursive, bCanSkipInvisible); + SwAccessibleContext::Dispose( bRecursive ); +} + +void SwAccessibleCell::InvalidatePosOrSize( const SwRect& rOldBox ) +{ + const SwFrame *pParent = GetParent( SwAccessibleChild(GetFrame()), IsInPagePreview() ); + ::rtl::Reference< SwAccessibleContext > xAccImpl( + GetMap()->GetContextImpl( pParent, false ) ); + if( xAccImpl.is() ) + xAccImpl->InvalidateChildPosOrSize( SwAccessibleChild(GetFrame()), rOldBox ); + SwAccessibleContext::InvalidatePosOrSize( rOldBox ); +} + +// XAccessibleInterface + +uno::Any SwAccessibleCell::queryInterface( const uno::Type& rType ) +{ + if (rType == cppu::UnoType<XAccessibleExtendedAttributes>::get()) + { + uno::Any aR; + aR <<= uno::Reference<XAccessibleExtendedAttributes>(this); + return aR; + } + + if (rType == cppu::UnoType<XAccessibleSelection>::get()) + { + uno::Any aR; + aR <<= uno::Reference<XAccessibleSelection>(this); + return aR; + } + if ( rType == ::cppu::UnoType<XAccessibleValue>::get() ) + { + uno::Reference<XAccessibleValue> xValue = this; + uno::Any aRet; + aRet <<= xValue; + return aRet; + } + else + { + return SwAccessibleContext::queryInterface( rType ); + } +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SwAccessibleCell::getTypes() +{ + return cppu::OTypeCollection( + ::cppu::UnoType<XAccessibleValue>::get(), + SwAccessibleContext::getTypes() ).getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleCell::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XAccessibleValue + +SwFrameFormat* SwAccessibleCell::GetTableBoxFormat() const +{ + assert(GetFrame()); + assert(GetFrame()->IsCellFrame()); + + const SwCellFrame* pCellFrame = static_cast<const SwCellFrame*>( GetFrame() ); + return pCellFrame->GetTabBox()->GetFrameFormat(); +} + +//Implement TableCell currentValue +uno::Any SwAccessibleCell::getCurrentValue( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return uno::Any( GetTableBoxFormat()->GetTableBoxValue().GetValue() ); +} + +sal_Bool SwAccessibleCell::setCurrentValue( const uno::Any& aNumber ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + double fValue = 0; + bool bValid = (aNumber >>= fValue); + if( bValid ) + { + SwTableBoxValue aValue( fValue ); + GetTableBoxFormat()->SetFormatAttr( aValue ); + } + return bValid; +} + +uno::Any SwAccessibleCell::getMaximumValue( ) +{ + return uno::Any(DBL_MAX); +} + +uno::Any SwAccessibleCell::getMinimumValue( ) +{ + return uno::Any(-DBL_MAX); +} + +uno::Any SwAccessibleCell::getMinimumIncrement( ) +{ + return uno::Any(); +} + +static OUString ReplaceOneChar(const OUString& oldOUString, std::u16string_view replacedChar, std::u16string_view replaceStr) +{ + int iReplace = oldOUString.lastIndexOf(replacedChar); + OUString aRet = oldOUString; + while(iReplace > -1) + { + aRet = aRet.replaceAt(iReplace,1, replaceStr); + iReplace = aRet.lastIndexOf(replacedChar,iReplace); + } + return aRet; +} + +static OUString ReplaceFourChar(const OUString& oldOUString) +{ + OUString aRet = ReplaceOneChar(oldOUString, u"\\", u"\\\\"); + aRet = ReplaceOneChar(aRet, u";", u"\\;"); + aRet = ReplaceOneChar(aRet, u"=", u"\\="); + aRet = ReplaceOneChar(aRet, u",", u"\\,"); + aRet = ReplaceOneChar(aRet, u":", u"\\:"); + return aRet; +} + +css::uno::Any SAL_CALL SwAccessibleCell::getExtendedAttributes() +{ + SolarMutexGuard g; + + css::uno::Any strRet; + SwFrameFormat *pFrameFormat = GetTableBoxFormat(); + assert(pFrameFormat); + + const SwTableBoxFormula& tbl_formula = pFrameFormat->GetTableBoxFormula(); + + OUString strFormula = ReplaceFourChar(tbl_formula.GetFormula()); + OUString strFor = "Formula:" + strFormula + ";"; + strRet <<= strFor; + + return strRet; +} + +sal_Int32 SAL_CALL SwAccessibleCell::getBackground() +{ + SolarMutexGuard g; + + const SvxBrushItem &rBack = GetFrame()->GetAttrSet()->GetBackground(); + Color crBack = rBack.GetColor(); + + if (COL_AUTO == crBack) + { + uno::Reference<XAccessible> xAccDoc = getAccessibleParent(); + if (xAccDoc.is()) + { + uno::Reference<XAccessibleComponent> xComponentDoc(xAccDoc, uno::UNO_QUERY); + if (xComponentDoc.is()) + { + crBack = Color(ColorTransparency, xComponentDoc->getBackground()); + } + } + } + return sal_Int32(crBack); +} + +// XAccessibleSelection +void SwAccessibleCell::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + m_aSelectionHelper.selectAccessibleChild(nChildIndex); +} + +sal_Bool SwAccessibleCell::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + return m_aSelectionHelper.isAccessibleChildSelected(nChildIndex); +} + +void SwAccessibleCell::clearAccessibleSelection( ) +{ +} + +void SwAccessibleCell::selectAllAccessibleChildren( ) +{ + m_aSelectionHelper.selectAllAccessibleChildren(); +} + +sal_Int32 SwAccessibleCell::getSelectedAccessibleChildCount( ) +{ + return m_aSelectionHelper.getSelectedAccessibleChildCount(); +} + +uno::Reference<XAccessible> SwAccessibleCell::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + return m_aSelectionHelper.getSelectedAccessibleChild(nSelectedChildIndex); +} + +void SwAccessibleCell::deselectAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + m_aSelectionHelper.deselectAccessibleChild(nSelectedChildIndex); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acccell.hxx b/sw/source/core/access/acccell.hxx new file mode 100644 index 000000000..b000d4958 --- /dev/null +++ b/sw/source/core/access/acccell.hxx @@ -0,0 +1,133 @@ +/* -*- 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 . + */ + +#pragma once + +#include "acccontext.hxx" +#include <com/sun/star/accessibility/XAccessibleValue.hpp> +#include "accselectionhelper.hxx" + +class SwCellFrame; +class SwAccessibleTable; +class SwFrameFormat; + +class SwAccessibleCell : public SwAccessibleContext, + public css::accessibility::XAccessibleValue, + public css::accessibility::XAccessibleSelection, + public css::accessibility::XAccessibleExtendedAttributes +{ + // Implementation for XAccessibleSelection interface + SwAccessibleSelectionHelper m_aSelectionHelper; + bool m_bIsSelected; // protected by base class mutex + + bool IsSelected(); + + bool InvalidateMyCursorPos(); + bool InvalidateChildrenCursorPos( const SwFrame *pFrame ); + + rtl::Reference<SwAccessibleTable> m_pAccTable; + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets SELECTABLE(1) and SELECTED(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual void InvalidateCursorPos_() override; + + virtual ~SwAccessibleCell() override; + +public: + SwAccessibleCell(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwCellFrame *pCellFrame); + + virtual bool HasCursor() override; // required by map to remember that object + + // XAccessibleContext + + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XServiceInfo + + // Returns an identifier for the implementation of this object. + virtual OUString SAL_CALL + getImplementationName() override; + + // Return whether the specified service is supported by this class. + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // Returns a list of all supported services. In this case that is just + // the AccessibleContext service. + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + + virtual void InvalidatePosOrSize( const SwRect& rFrame ) override; + + // XInterface + + // (XInterface methods need to be implemented to disambiguate + // between those inherited through SwAccessibleContext and + // XAccessibleValue). + + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) noexcept override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) noexcept override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleExtendedAttributes + css::uno::Any SAL_CALL getExtendedAttributes() override ; +private: + SwFrameFormat* GetTableBoxFormat() const; + +public: + // XAccessibleValue + virtual css::uno::Any SAL_CALL getCurrentValue( ) override; + virtual sal_Bool SAL_CALL setCurrentValue( const css::uno::Any& aNumber ) override; + virtual css::uno::Any SAL_CALL getMaximumValue( ) override; + virtual css::uno::Any SAL_CALL getMinimumValue( ) override; + virtual css::uno::Any SAL_CALL getMinimumIncrement( ) override; + + // XAccessibleComponent + sal_Int32 SAL_CALL getBackground() override; + + // XAccessibleSelection + virtual void SAL_CALL selectAccessibleChild( sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( sal_Int32 nChildIndex ) override; + virtual void SAL_CALL clearAccessibleSelection( ) override; + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + virtual void SAL_CALL deselectAccessibleChild( sal_Int32 nSelectedChildIndex ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acccontext.cxx b/sw/source/core/access/acccontext.cxx new file mode 100644 index 000000000..601e05a98 --- /dev/null +++ b/sw/source/core/access/acccontext.cxx @@ -0,0 +1,1519 @@ +/* -*- 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 . + */ + +#include <vcl/window.hxx> +#include <swtypes.hxx> + +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleStateSet.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <viewsh.hxx> +#include <crsrsh.hxx> +#include <fesh.hxx> +#include <wrtsh.hxx> +#include <txtfrm.hxx> +#include <ndtxt.hxx> +#include <pagefrm.hxx> +#include <flyfrm.hxx> +#include <dflyobj.hxx> +#include <pam.hxx> +#include <accmap.hxx> +#include "accfrmobjslist.hxx" +#include "acccontext.hxx" +#include <svx/AccessibleShape.hxx> +#include <comphelper/accessibleeventnotifier.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <PostItMgr.hxx> + +using namespace sw::access; +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +void SwAccessibleContext::InitStates() +{ + m_isShowingState = GetMap() && IsShowing( *(GetMap()) ); + + SwViewShell *pVSh = GetMap()->GetShell(); + m_isEditableState = pVSh && IsEditable( pVSh ); + m_isOpaqueState = pVSh && IsOpaque( pVSh ); + m_isDefuncState = false; +} + +void SwAccessibleContext::SetParent( SwAccessibleContext *pParent ) +{ + std::scoped_lock aGuard( m_Mutex ); + + uno::Reference < XAccessible > xParent( pParent ); + m_xWeakParent = xParent; +} + +uno::Reference< XAccessible > SwAccessibleContext::GetWeakParent() const +{ + std::scoped_lock aGuard( m_Mutex ); + + uno::Reference< XAccessible > xParent( m_xWeakParent ); + return xParent; +} + +vcl::Window *SwAccessibleContext::GetWindow() +{ + vcl::Window *pWin = nullptr; + + if( GetMap() ) + { + const SwViewShell *pVSh = GetMap()->GetShell(); + OSL_ENSURE( pVSh, "no view shell" ); + if( pVSh ) + pWin = pVSh->GetWin(); + + OSL_ENSURE( pWin, "no window" ); + } + + return pWin; +} + +// get SwViewShell from accessibility map, and cast to cursor shell +SwCursorShell* SwAccessibleContext::GetCursorShell() +{ + SwViewShell* pViewShell = GetMap() ? GetMap()->GetShell() : nullptr; + OSL_ENSURE( pViewShell, "no view shell" ); + return dynamic_cast<SwCursorShell*>( pViewShell); +} + +const SwCursorShell* SwAccessibleContext::GetCursorShell() const +{ + // just like non-const GetCursorShell + const SwViewShell* pViewShell = GetMap() ? GetMap()->GetShell() : nullptr; + OSL_ENSURE( pViewShell, "no view shell" ); + return dynamic_cast<const SwCursorShell*>( pViewShell); +} + +namespace { + +enum class Action { NONE, SCROLLED, SCROLLED_WITHIN, + SCROLLED_IN, SCROLLED_OUT }; + +} + +void SwAccessibleContext::ChildrenScrolled( const SwFrame *pFrame, + const SwRect& rOldVisArea ) +{ + const SwRect& rNewVisArea = GetVisArea(); + const bool bVisibleChildrenOnly = SwAccessibleChild( pFrame ).IsVisibleChildrenOnly(); + + const SwAccessibleChildSList aList( *pFrame, *(GetMap()) ); + SwAccessibleChildSList::const_iterator aIter( aList.begin() ); + while( aIter != aList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + const SwRect aBox( rLower.GetBox( *(GetMap()) ) ); + if( rLower.IsAccessible( GetShell()->IsPreview() ) ) + { + Action eAction = Action::NONE; + if( aBox.Overlaps( rNewVisArea ) ) + { + if( aBox.Overlaps( rOldVisArea ) ) + { + eAction = Action::SCROLLED_WITHIN; + } + else + { + if ( bVisibleChildrenOnly && + !rLower.AlwaysIncludeAsChild() ) + { + eAction = Action::SCROLLED_IN; + } + else + { + eAction = Action::SCROLLED; + } + } + } + else if( aBox.Overlaps( rOldVisArea ) ) + { + if ( bVisibleChildrenOnly && + !rLower.AlwaysIncludeAsChild() ) + { + eAction = Action::SCROLLED_OUT; + } + else + { + eAction = Action::SCROLLED; + } + } + else if( !bVisibleChildrenOnly || + rLower.AlwaysIncludeAsChild() ) + { + // This wouldn't be required if the SwAccessibleFrame, + // wouldn't know about the visible area. + eAction = Action::SCROLLED; + } + if( Action::NONE != eAction ) + { + if ( rLower.GetSwFrame() ) + { + OSL_ENSURE( !rLower.AlwaysIncludeAsChild(), + "<SwAccessibleContext::ChildrenScrolled(..)> - always included child not considered!" ); + const SwFrame* pLower( rLower.GetSwFrame() ); + ::rtl::Reference< SwAccessibleContext > xAccImpl = + GetMap()->GetContextImpl( pLower ); + if( xAccImpl.is() ) + { + switch( eAction ) + { + case Action::SCROLLED: + xAccImpl->Scrolled( rOldVisArea ); + break; + case Action::SCROLLED_WITHIN: + xAccImpl->ScrolledWithin( rOldVisArea ); + break; + case Action::SCROLLED_IN: + xAccImpl->ScrolledIn(); + break; + case Action::SCROLLED_OUT: + xAccImpl->ScrolledOut( rOldVisArea ); + break; + case Action::NONE: + break; + } + } + else + { + ChildrenScrolled( pLower, rOldVisArea ); + } + } + else if ( rLower.GetDrawObject() ) + { + OSL_ENSURE( !rLower.AlwaysIncludeAsChild(), + "<SwAccessibleContext::ChildrenScrolled(..)> - always included child not considered!" ); + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl = + GetMap()->GetContextImpl( rLower.GetDrawObject(), + this ); + if( xAccImpl.is() ) + { + switch( eAction ) + { + case Action::SCROLLED: + case Action::SCROLLED_WITHIN: + xAccImpl->ViewForwarderChanged(); + break; + case Action::SCROLLED_IN: + ScrolledInShape( xAccImpl.get() ); + break; + case Action::SCROLLED_OUT: + { + xAccImpl->ViewForwarderChanged(); + // this DisposeShape call was removed by + // IAccessibility2 implementation + // without giving any reason why + DisposeShape( rLower.GetDrawObject(), + xAccImpl.get() ); + } + break; + // coverity[dead_error_begin] - following conditions exist to avoid compiler warning + case Action::NONE: + break; + } + } + } + else if ( rLower.GetWindow() ) + { + // nothing to do - as such children are always included as children. + OSL_ENSURE( rLower.AlwaysIncludeAsChild(), + "<SwAccessibleContext::ChildrenScrolled(..)> - not always included child not considered!" ); + } + } + } + else if ( rLower.GetSwFrame() && + ( !bVisibleChildrenOnly || + aBox.Overlaps( rOldVisArea ) || + aBox.Overlaps( rNewVisArea ) ) ) + { + // There are no unaccessible SdrObjects that need to be notified + ChildrenScrolled( rLower.GetSwFrame(), rOldVisArea ); + } + ++aIter; + } +} + +void SwAccessibleContext::Scrolled( const SwRect& rOldVisArea ) +{ + SetVisArea( GetMap()->GetVisArea() ); + + ChildrenScrolled( GetFrame(), rOldVisArea ); + + bool bIsOldShowingState; + bool bIsNewShowingState = IsShowing( *(GetMap()) ); + { + std::scoped_lock aGuard( m_Mutex ); + bIsOldShowingState = m_isShowingState; + m_isShowingState = bIsNewShowingState; + } + + if( bIsOldShowingState != bIsNewShowingState ) + FireStateChangedEvent( AccessibleStateType::SHOWING, + bIsNewShowingState ); +} + +void SwAccessibleContext::ScrolledWithin( const SwRect& rOldVisArea ) +{ + SetVisArea( GetMap()->GetVisArea() ); + + ChildrenScrolled( GetFrame(), rOldVisArea ); + + FireVisibleDataEvent(); +} + +void SwAccessibleContext::ScrolledIn() +{ + // This accessible should be freshly created, because it + // was not visible before. Therefore, its visible area must already + // reflect the scrolling. + OSL_ENSURE( GetVisArea() == GetMap()->GetVisArea(), + "Visible area of child is wrong. Did it exist already?" ); + + // Send child event at parent. That's all we have to do here. + const SwFrame* pParent = GetParent(); + ::rtl::Reference< SwAccessibleContext > xParentImpl( + GetMap()->GetContextImpl( pParent, false ) ); + uno::Reference < XAccessibleContext > xThis( this ); + if( !xParentImpl.is() ) + return; + + SetParent( xParentImpl.get() ); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= xThis; + + xParentImpl->FireAccessibleEvent( aEvent ); + + if( HasCursor() ) + { + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + { + FireStateChangedEvent( AccessibleStateType::FOCUSED, true ); + } + } +} + +void SwAccessibleContext::ScrolledOut( const SwRect& rOldVisArea ) +{ + SetVisArea( GetMap()->GetVisArea() ); + + // First of all, update the children. That's required to dispose + // all children that are existing only if they are visible. They + // are not disposed by the recursive Dispose call that follows later on, + // because this call will only dispose children that are in the + // new visible area. The children we want to dispose however are in the + // old visible area all. + ChildrenScrolled( GetFrame(), rOldVisArea ); + + // Broadcast a state changed event for the showing state. + // It might be that the child is freshly created just to send + // the child event. In this case no listener will exist. + FireStateChangedEvent( AccessibleStateType::SHOWING, false ); + + // this Dispose call was removed by IAccessibility2 implementation + // without giving any reason why - without it we get stale + // entries in SwAccessibleMap::mpFrameMap. + Dispose(true); +} + +// #i27301# - use new type definition for <_nStates> +void SwAccessibleContext::InvalidateChildrenStates( const SwFrame* _pFrame, + AccessibleStates _nStates ) +{ + const SwAccessibleChildSList aVisList( GetVisArea(), *_pFrame, *(GetMap()) ); + + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame* pLower = rLower.GetSwFrame(); + if( pLower ) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl; + if( rLower.IsAccessible( GetShell()->IsPreview() ) ) + xAccImpl = GetMap()->GetContextImpl( pLower, false ); + if( xAccImpl.is() ) + xAccImpl->InvalidateStates( _nStates ); + else + InvalidateChildrenStates( pLower, _nStates ); + } + else if ( rLower.GetDrawObject() ) + { + // TODO: SdrObjects + } + else if ( rLower.GetWindow() ) + { + // nothing to do ? + } + + ++aIter; + } +} + +void SwAccessibleContext::DisposeChildren(const SwFrame *pFrame, + bool bRecursive, + bool bCanSkipInvisible) +{ + const SwAccessibleChildSList aVisList( GetVisArea(), *pFrame, *(GetMap()) ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame* pLower = rLower.GetSwFrame(); + if( pLower ) + { + // tdf#117601 dispose the darn thing if it ever was accessible + ::rtl::Reference<SwAccessibleContext> xAccImpl = GetMap()->GetContextImpl(pLower, false); + if( xAccImpl.is() ) + xAccImpl->Dispose( bRecursive ); + else + { + // it's possible that the xAccImpl *does* exist with a + // ref-count of 0 and blocked in its dtor in another thread - + // this call here could be from SwAccessibleMap dtor so + // remove it from any maps now! + GetMap()->RemoveContext(pLower); + // in this case the context will check with a weak_ptr + // that the map is still alive so it's not necessary + // to clear its m_pMap here. + if (bRecursive) + { + DisposeChildren(pLower, bRecursive, bCanSkipInvisible); + } + } + } + else if ( rLower.GetDrawObject() ) + { + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl( + GetMap()->GetContextImpl( rLower.GetDrawObject(), + this, false ) ); + if( xAccImpl.is() ) + DisposeShape( rLower.GetDrawObject(), xAccImpl.get() ); + } + else if ( rLower.GetWindow() ) + { + DisposeChild(rLower, false, bCanSkipInvisible); + } + ++aIter; + } +} + +void SwAccessibleContext::InvalidateContent_( bool ) +{ +} + +void SwAccessibleContext::InvalidateCursorPos_() +{ +} + +void SwAccessibleContext::InvalidateFocus_() +{ +} + +void SwAccessibleContext::FireAccessibleEvent( AccessibleEventObject& rEvent ) +{ + OSL_ENSURE( GetFrame(), "fire event for disposed frame?" ); + if( !GetFrame() ) + return; + + if( !rEvent.Source.is() ) + { + uno::Reference < XAccessibleContext > xThis( this ); + rEvent.Source = xThis; + } + + if (m_nClientId) + comphelper::AccessibleEventNotifier::addEvent( m_nClientId, rEvent ); +} + +void SwAccessibleContext::FireVisibleDataEvent() +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::VISIBLE_DATA_CHANGED; + + FireAccessibleEvent( aEvent ); +} + +void SwAccessibleContext::FireStateChangedEvent( sal_Int16 nState, + bool bNewState ) +{ + AccessibleEventObject aEvent; + + aEvent.EventId = AccessibleEventId::STATE_CHANGED; + if( bNewState ) + aEvent.NewValue <<= nState; + else + aEvent.OldValue <<= nState; + + FireAccessibleEvent( aEvent ); +} + +void SwAccessibleContext::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SolarMutexGuard aGuard; + + // SHOWING + if (m_isShowingState) + rStateSet.AddState( AccessibleStateType::SHOWING ); + + // EDITABLE + if (m_isEditableState) + //Set editable state to graphic and other object when the document is editable + { + rStateSet.AddState( AccessibleStateType::EDITABLE ); + rStateSet.AddState( AccessibleStateType::RESIZABLE ); + rStateSet.AddState( AccessibleStateType::MOVEABLE ); + } + // ENABLED + rStateSet.AddState( AccessibleStateType::ENABLED ); + + // OPAQUE + if (m_isOpaqueState) + rStateSet.AddState( AccessibleStateType::OPAQUE ); + + // VISIBLE + rStateSet.AddState( AccessibleStateType::VISIBLE ); + + if (m_isDefuncState) + rStateSet.AddState( AccessibleStateType::DEFUNC ); +} + +bool SwAccessibleContext::IsEditableState() +{ + bool bRet; + { + std::scoped_lock aGuard( m_Mutex ); + bRet = m_isEditableState; + } + + return bRet; +} + +void SwAccessibleContext::ThrowIfDisposed() +{ + if (!(GetFrame() && GetMap())) + { + throw lang::DisposedException("object is nonfunctional", + static_cast<cppu::OWeakObject*>(this)); + } +} + +SwAccessibleContext::SwAccessibleContext(std::shared_ptr<SwAccessibleMap> const& pMap, + sal_Int16 const nRole, + const SwFrame *pF ) + : SwAccessibleFrame( pMap->GetVisArea(), pF, + pMap->GetShell()->IsPreview() ) + , m_pMap(pMap.get()) + , m_wMap(pMap) + , m_nClientId(0) + , m_nRole(nRole) + , m_isDisposing( false ) + , m_isRegisteredAtAccessibleMap( true ) + , m_isSelectedInDoc(false) +{ + InitStates(); +} + +SwAccessibleContext::~SwAccessibleContext() +{ + // must have for 2 reasons: 2. as long as this thread has SolarMutex + // another thread cannot destroy the SwAccessibleMap so our temporary + // taking a hard ref to SwAccessibleMap won't delay its destruction + SolarMutexGuard aGuard; + // must check with weak_ptr that m_pMap is still alive + std::shared_ptr<SwAccessibleMap> pMap(m_wMap.lock()); + if (m_isRegisteredAtAccessibleMap && GetFrame() && pMap) + { + pMap->RemoveContext( GetFrame() ); + } +} + +uno::Reference< XAccessibleContext > SAL_CALL + SwAccessibleContext::getAccessibleContext() +{ + uno::Reference < XAccessibleContext > xRet( this ); + return xRet; +} + +sal_Int32 SAL_CALL SwAccessibleContext::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return m_isDisposing ? 0 : GetChildCount( *(GetMap()) ); +} + +uno::Reference< XAccessible> SAL_CALL + SwAccessibleContext::getAccessibleChild( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwAccessibleChild aChild( GetChild( *(GetMap()), nIndex ) ); + if( !aChild.IsValid() ) + { + uno::Reference < XAccessibleContext > xThis( this ); + lang::IndexOutOfBoundsException aExcept( + "index out of bounds", + xThis ); + throw aExcept; + } + + uno::Reference< XAccessible > xChild; + if( aChild.GetSwFrame() ) + { + ::rtl::Reference < SwAccessibleContext > xChildImpl( + GetMap()->GetContextImpl( aChild.GetSwFrame(), !m_isDisposing ) ); + if( xChildImpl.is() ) + { + xChildImpl->SetParent( this ); + xChild = xChildImpl.get(); + } + } + else if ( aChild.GetDrawObject() ) + { + ::rtl::Reference < ::accessibility::AccessibleShape > xChildImpl( + GetMap()->GetContextImpl( aChild.GetDrawObject(), + this, !m_isDisposing) ); + if( xChildImpl.is() ) + xChild = xChildImpl.get(); + } + else if ( aChild.GetWindow() ) + { + xChild = aChild.GetWindow()->GetAccessible(); + } + + return xChild; +} + +css::uno::Sequence<uno::Reference<XAccessible>> SAL_CALL + SwAccessibleContext::getAccessibleChildren() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + std::list< sw::access::SwAccessibleChild > aChildren; + GetChildren( *GetMap(), aChildren ); + + std::vector<uno::Reference<XAccessible>> aRet; + aRet.reserve(aChildren.size()); + for (const auto & rSwChild : aChildren) + { + uno::Reference< XAccessible > xChild; + if( rSwChild.GetSwFrame() ) + { + ::rtl::Reference < SwAccessibleContext > xChildImpl( + GetMap()->GetContextImpl( rSwChild.GetSwFrame(), !m_isDisposing ) ); + if( xChildImpl.is() ) + { + xChildImpl->SetParent( this ); + xChild = xChildImpl.get(); + } + } + else if ( rSwChild.GetDrawObject() ) + { + ::rtl::Reference < ::accessibility::AccessibleShape > xChildImpl( + GetMap()->GetContextImpl( rSwChild.GetDrawObject(), + this, !m_isDisposing) ); + if( xChildImpl.is() ) + xChild = xChildImpl.get(); + } + else if ( rSwChild.GetWindow() ) + { + xChild = rSwChild.GetWindow()->GetAccessible(); + } + aRet.push_back(xChild); + } + return comphelper::containerToSequence(aRet); +} + +uno::Reference< XAccessible> SwAccessibleContext::getAccessibleParentImpl() +{ + SolarMutexGuard aGuard; + + const SwFrame *pUpper = GetParent(); + OSL_ENSURE( pUpper != nullptr || m_isDisposing, "no upper found" ); + + uno::Reference< XAccessible > xAcc; + if( pUpper ) + xAcc = GetMap()->GetContext( pUpper, !m_isDisposing ); + + OSL_ENSURE( xAcc.is() || m_isDisposing, "no parent found" ); + + // Remember the parent as weak ref. + { + std::scoped_lock aWeakParentGuard( m_Mutex ); + m_xWeakParent = xAcc; + } + + return xAcc; +} + +uno::Reference< XAccessible> SAL_CALL SwAccessibleContext::getAccessibleParent() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return getAccessibleParentImpl(); +} + +sal_Int32 SAL_CALL SwAccessibleContext::getAccessibleIndexInParent() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwFrame *pUpper = GetParent(); + OSL_ENSURE( pUpper != nullptr || m_isDisposing, "no upper found" ); + + sal_Int32 nIndex = -1; + if( pUpper ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl( + GetMap()->GetContextImpl(pUpper, !m_isDisposing) ); + OSL_ENSURE( xAccImpl.is() || m_isDisposing, "no parent found" ); + if( xAccImpl.is() ) + nIndex = xAccImpl->GetChildIndex( *(GetMap()), SwAccessibleChild(GetFrame()) ); + } + + return nIndex; +} + +sal_Int16 SAL_CALL SwAccessibleContext::getAccessibleRole() +{ + return m_nRole; +} + +OUString SAL_CALL SwAccessibleContext::getAccessibleName() +{ + return m_sName; +} + +uno::Reference< XAccessibleRelationSet> SAL_CALL + SwAccessibleContext::getAccessibleRelationSet() +{ + // by default there are no relations + uno::Reference< XAccessibleRelationSet> xRet( new utl::AccessibleRelationSetHelper() ); + return xRet; +} + +uno::Reference<XAccessibleStateSet> SAL_CALL + SwAccessibleContext::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + rtl::Reference<::utl::AccessibleStateSetHelper> pStateSet = + new ::utl::AccessibleStateSetHelper; + + if (m_isSelectedInDoc) + pStateSet->AddState( AccessibleStateType::SELECTED ); + + GetStates( *pStateSet ); + + return pStateSet; +} + +lang::Locale SAL_CALL SwAccessibleContext::getLocale() +{ + SolarMutexGuard aGuard; + + lang::Locale aLoc( Application::GetSettings().GetLanguageTag().getLocale() ); + return aLoc; +} + +void SAL_CALL SwAccessibleContext::addAccessibleEventListener( + const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (xListener.is()) + { + SolarMutexGuard aGuard; + if (!m_nClientId) + m_nClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( m_nClientId, xListener ); + } +} + +void SAL_CALL SwAccessibleContext::removeAccessibleEventListener( + const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (!(xListener.is() && m_nClientId)) + return; + + SolarMutexGuard aGuard; + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( m_nClientId, xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + comphelper::AccessibleEventNotifier::revokeClient( m_nClientId ); + m_nClientId = 0; + } +} + +static bool lcl_PointInRectangle(const awt::Point & aPoint, + const awt::Rectangle & aRect) +{ + tools::Long nDiffX = aPoint.X - aRect.X; + tools::Long nDiffY = aPoint.Y - aRect.Y; + + return + nDiffX >= 0 && nDiffX < aRect.Width && nDiffY >= 0 && + nDiffY < aRect.Height; + +} + +sal_Bool SAL_CALL SwAccessibleContext::containsPoint( + const awt::Point& aPoint ) +{ + awt::Rectangle aPixBounds = getBoundsImpl(true); + aPixBounds.X = 0; + aPixBounds.Y = 0; + + return lcl_PointInRectangle(aPoint, aPixBounds); +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleContext::getAccessibleAtPoint( + const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + uno::Reference< XAccessible > xAcc; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + Point aPixPoint( aPoint.X, aPoint.Y ); // px rel to parent + if( !GetFrame()->IsRootFrame() ) + { + SwRect aLogBounds( GetBounds( *(GetMap()), GetFrame() ) ); // twip rel to doc root + Point aPixPos( GetMap()->CoreToPixel( aLogBounds ).TopLeft() ); + aPixPoint.setX(aPixPoint.getX() + aPixPos.getX()); + aPixPoint.setY(aPixPoint.getY() + aPixPos.getY()); + } + + const SwAccessibleChild aChild( GetChildAtPixel( aPixPoint, *(GetMap()) ) ); + if( aChild.GetSwFrame() ) + { + xAcc = GetMap()->GetContext( aChild.GetSwFrame() ); + } + else if( aChild.GetDrawObject() ) + { + xAcc = GetMap()->GetContext( aChild.GetDrawObject(), this ); + } + else if ( aChild.GetWindow() ) + { + xAcc = aChild.GetWindow()->GetAccessible(); + } + + return xAcc; +} + +/** + Get bounding box. + + There are two modes. + + - relative + + Return bounding box relative to parent if parent is no root + frame. Otherwise return the absolute bounding box. + + - absolute + + Return the absolute bounding box. + + @param bRelative + true: Use relative mode. + false: Use absolute mode. +*/ +awt::Rectangle SwAccessibleContext::getBoundsImpl(bool bRelative) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwFrame *pParent = GetParent(); + OSL_ENSURE( pParent, "no Parent found" ); + vcl::Window *pWin = GetWindow(); + + if (!pParent) + { + throw uno::RuntimeException("no Parent", static_cast<cppu::OWeakObject*>(this)); + } + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + SwRect aLogBounds( GetBounds( *(GetMap()), GetFrame() ) ); // twip relative to document root + tools::Rectangle aPixBounds( 0, 0, 0, 0 ); + if( GetFrame()->IsPageFrame() && + static_cast < const SwPageFrame * >( GetFrame() )->IsEmptyPage() ) + { + OSL_ENSURE( GetShell()->IsPreview(), "empty page accessible?" ); + if( GetShell()->IsPreview() ) + { + // adjust method call <GetMap()->GetPreviewPageSize()> + sal_uInt16 nPageNum = + static_cast < const SwPageFrame * >( GetFrame() )->GetPhyPageNum(); + aLogBounds.SSize( GetMap()->GetPreviewPageSize( nPageNum ) ); + } + } + if( !aLogBounds.IsEmpty() ) + { + aPixBounds = GetMap()->CoreToPixel( aLogBounds ); + if( !pParent->IsRootFrame() && bRelative) + { + SwRect aParentLogBounds( GetBounds( *(GetMap()), pParent ) ); // twip rel to doc root + Point aParentPixPos( GetMap()->CoreToPixel( aParentLogBounds ).TopLeft() ); + aPixBounds.Move( -aParentPixPos.getX(), -aParentPixPos.getY() ); + } + } + + awt::Rectangle aBox( aPixBounds.Left(), aPixBounds.Top(), + aPixBounds.GetWidth(), aPixBounds.GetHeight() ); + + return aBox; +} + +awt::Rectangle SAL_CALL SwAccessibleContext::getBounds() +{ + return getBoundsImpl(true); +} + +awt::Point SAL_CALL SwAccessibleContext::getLocation() +{ + awt::Rectangle aRect = getBoundsImpl(true); + awt::Point aPoint(aRect.X, aRect.Y); + + return aPoint; +} + +awt::Point SAL_CALL SwAccessibleContext::getLocationOnScreen() +{ + awt::Rectangle aRect = getBoundsImpl(false); + + Point aPixPos(aRect.X, aRect.Y); + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + aPixPos = pWin->OutputToAbsoluteScreenPixel(aPixPos); + awt::Point aPoint(aPixPos.getX(), aPixPos.getY()); + + return aPoint; +} + +awt::Size SAL_CALL SwAccessibleContext::getSize() +{ + awt::Rectangle aRect = getBoundsImpl(false); + awt::Size aSize( aRect.Width, aRect.Height ); + + return aSize; +} + +void SAL_CALL SwAccessibleContext::grabFocus() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( GetFrame()->IsFlyFrame() ) + { + const SdrObject *pObj = + static_cast < const SwFlyFrame * >( GetFrame() )->GetVirtDrawObj(); + if( pObj ) + Select( const_cast < SdrObject * >( pObj ), false ); + } + else + { + const SwContentFrame *pCFrame = nullptr; + if( GetFrame()->IsContentFrame() ) + pCFrame = static_cast< const SwContentFrame * >( GetFrame() ); + else if( GetFrame()->IsLayoutFrame() ) + pCFrame = static_cast< const SwLayoutFrame * >( GetFrame() )->ContainsContent(); + + if( pCFrame && pCFrame->IsTextFrame() ) + { + const SwTextFrame *pTextFrame = static_cast< const SwTextFrame * >( pCFrame ); + const SwTextNode *pTextNd = pTextFrame->GetTextNodeFirst(); + assert(pTextNd); // can it actually be null? probably not=>simplify + if( pTextNd ) + { + // create pam for selection + SwPosition const aStartPos(pTextFrame->MapViewToModelPos(pTextFrame->GetOffset())); + SwPaM aPaM( aStartPos ); + + // set PaM at cursor shell + Select( aPaM ); + } + } + } +} + +sal_Int32 SAL_CALL SwAccessibleContext::getForeground() +{ + return sal_Int32(COL_BLACK); +} + +sal_Int32 SAL_CALL SwAccessibleContext::getBackground() +{ + return sal_Int32(COL_WHITE); +} + +sal_Bool SAL_CALL SwAccessibleContext::supportsService (const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +void SwAccessibleContext::DisposeShape( const SdrObject *pObj, + ::accessibility::AccessibleShape *pAccImpl ) +{ + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl( pAccImpl ); + if( !xAccImpl.is() ) + xAccImpl = GetMap()->GetContextImpl( pObj, this ); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + uno::Reference< XAccessible > xAcc( xAccImpl ); + aEvent.OldValue <<= xAcc; + FireAccessibleEvent( aEvent ); + + GetMap()->RemoveContext( pObj ); + xAccImpl->dispose(); +} + +void SwAccessibleContext::ScrolledInShape( ::accessibility::AccessibleShape *pAccImpl ) +{ + if(nullptr == pAccImpl) + { + return ; + } + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + uno::Reference< XAccessible > xAcc( pAccImpl ); + aEvent.NewValue <<= xAcc; + FireAccessibleEvent( aEvent ); + + if( !pAccImpl->GetState( AccessibleStateType::FOCUSED ) ) + return; + + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + { + AccessibleEventObject aStateChangedEvent; + aStateChangedEvent.EventId = AccessibleEventId::STATE_CHANGED; + aStateChangedEvent.NewValue <<= AccessibleStateType::FOCUSED; + aStateChangedEvent.Source = xAcc; + + FireAccessibleEvent( aStateChangedEvent ); + } +} + +void SwAccessibleContext::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + SolarMutexGuard aGuard; + + OSL_ENSURE( GetFrame() && GetMap(), "already disposed" ); + OSL_ENSURE( GetMap()->GetVisArea() == GetVisArea(), + "invalid visible area for dispose" ); + + m_isDisposing = true; + + // dispose children + if( bRecursive ) + DisposeChildren(GetFrame(), bRecursive, bCanSkipInvisible); + + // get parent + uno::Reference< XAccessible > xParent( GetWeakParent() ); + uno::Reference < XAccessibleContext > xThis( this ); + + // send child event at parent + if( xParent.is() ) + { + SwAccessibleContext *pAcc = static_cast<SwAccessibleContext *>(xParent.get()); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.OldValue <<= xThis; + pAcc->FireAccessibleEvent( aEvent ); + } + + // set defunc state (it's not required to broadcast a state changed + // event if the object is disposed afterwards) + { + std::scoped_lock aDefuncStateGuard( m_Mutex ); + m_isDefuncState = true; + } + + // broadcast dispose event + if (m_nClientId) + { + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( m_nClientId, *this ); + m_nClientId = 0; + } + + RemoveFrameFromAccessibleMap(); + ClearFrame(); + m_pMap = nullptr; + m_wMap.reset(); + + m_isDisposing = false; +} + +void SwAccessibleContext::DisposeChild( const SwAccessibleChild& rChildFrameOrObj, + bool bRecursive, bool bCanSkipInvisible ) +{ + SolarMutexGuard aGuard; + + if ( !bCanSkipInvisible || + rChildFrameOrObj.AlwaysIncludeAsChild() || + IsShowing( *(GetMap()), rChildFrameOrObj ) || + !SwAccessibleChild( GetFrame() ).IsVisibleChildrenOnly() ) + { + // If the object could have existed before, then there is nothing to do, + // because no wrapper exists now and therefore no one is interested to + // get notified of the movement. + if( rChildFrameOrObj.GetSwFrame() ) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetSwFrame(), false ); + if (xAccImpl) + xAccImpl->Dispose( bRecursive ); + } + else if ( rChildFrameOrObj.GetDrawObject() ) + { + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetDrawObject(), + this, false ); + if (xAccImpl) + DisposeShape( rChildFrameOrObj.GetDrawObject(), + xAccImpl.get() ); + } + else if ( rChildFrameOrObj.GetWindow() ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + uno::Reference< XAccessible > xAcc = + rChildFrameOrObj.GetWindow()->GetAccessible(); + aEvent.OldValue <<= xAcc; + FireAccessibleEvent( aEvent ); + } + } + else if( bRecursive && rChildFrameOrObj.GetSwFrame() ) + DisposeChildren(rChildFrameOrObj.GetSwFrame(), bRecursive, bCanSkipInvisible); +} + +void SwAccessibleContext::InvalidatePosOrSize( const SwRect& ) +{ + SolarMutexGuard aGuard; + + OSL_ENSURE( GetFrame() && !GetFrame()->getFrameArea().IsEmpty(), "context should have a size" ); + + bool bIsOldShowingState; + bool bIsNewShowingState = IsShowing( *(GetMap()) ); + { + std::scoped_lock aShowingStateGuard( m_Mutex ); + bIsOldShowingState = m_isShowingState; + m_isShowingState = bIsNewShowingState; + } + + if( bIsOldShowingState != bIsNewShowingState ) + { + FireStateChangedEvent( AccessibleStateType::SHOWING, + bIsNewShowingState ); + } + else if( bIsNewShowingState ) + { + // The frame stays visible -> broadcast event + FireVisibleDataEvent(); + } + + // note: InvalidatePosOrSize must call InvalidateContent_ so that + // SwAccessibleParagraph updates its portions, or dispose it + // (see accmap.cxx: INVALID_CONTENT is contained in POS_CHANGED) + if( !bIsNewShowingState && + SwAccessibleChild( GetParent() ).IsVisibleChildrenOnly() ) + { + // this Dispose call was removed by IAccessibility2 implementation + // without giving any reason why - without it we get stale + // entries in SwAccessibleMap::mpFrameMap. + Dispose(true); + } + else + { + InvalidateContent_( true ); + } +} + +void SwAccessibleContext::InvalidateChildPosOrSize( + const SwAccessibleChild& rChildFrameOrObj, + const SwRect& rOldFrame ) +{ + SolarMutexGuard aGuard; + + // this happens during layout, e.g. when a page is deleted and next page's + // header/footer moves backward such an event is generated + SAL_INFO_IF(rChildFrameOrObj.GetSwFrame() && + rChildFrameOrObj.GetSwFrame()->getFrameArea().IsEmpty(), + "sw.a11y", "child context should have a size"); + + if ( rChildFrameOrObj.AlwaysIncludeAsChild() ) + { + // nothing to do; + return; + } + + const bool bVisibleChildrenOnly = SwAccessibleChild( GetFrame() ).IsVisibleChildrenOnly(); + const bool bNew = rOldFrame.IsEmpty() || + ( rOldFrame.Left() == 0 && rOldFrame.Top() == 0 ); + if( IsShowing( *(GetMap()), rChildFrameOrObj ) ) + { + // If the object could have existed before, then there is nothing to do, + // because no wrapper exists now and therefore no one is interested to + // get notified of the movement. + if( bNew || (bVisibleChildrenOnly && !IsShowing( rOldFrame )) ) + { + if( rChildFrameOrObj.GetSwFrame() ) + { + // The frame becomes visible. A child event must be send. + ::rtl::Reference< SwAccessibleContext > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetSwFrame() ); + xAccImpl->ScrolledIn(); + } + else if ( rChildFrameOrObj.GetDrawObject() ) + { + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetDrawObject(), + this ); + // #i37790# + if ( xAccImpl.is() ) + { + ScrolledInShape( xAccImpl.get() ); + } + else + { + OSL_FAIL( "<SwAccessibleContext::InvalidateChildPosOrSize(..)> - no accessible shape found." ); + } + } + else if ( rChildFrameOrObj.GetWindow() ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= rChildFrameOrObj.GetWindow()->GetAccessible(); + FireAccessibleEvent( aEvent ); + } + } + } + else + { + // If the frame was visible before, then a child event for the parent + // needs to be send. However, there is no wrapper existing, and so + // no notifications for grandchildren are required. If the are + // grandgrandchildren, they would be notified by the layout. + if( bVisibleChildrenOnly && + !bNew && IsShowing( rOldFrame ) ) + { + if( rChildFrameOrObj.GetSwFrame() ) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetSwFrame() ); + xAccImpl->SetParent( this ); + xAccImpl->Dispose( true ); + } + else if ( rChildFrameOrObj.GetDrawObject() ) + { + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetDrawObject(), + this ); + DisposeShape( rChildFrameOrObj.GetDrawObject(), + xAccImpl.get() ); + } + else if ( rChildFrameOrObj.GetWindow() ) + { + OSL_FAIL( "<SwAccessibleContext::InvalidateChildPosOrSize(..)> - not expected to handle dispose of child of type <vcl::Window>." ); + } + } + } +} + +void SwAccessibleContext::InvalidateContent() +{ + SolarMutexGuard aGuard; + + InvalidateContent_( false ); +} + +void SwAccessibleContext::InvalidateCursorPos() +{ + SolarMutexGuard aGuard; + + InvalidateCursorPos_(); +} + +void SwAccessibleContext::InvalidateFocus() +{ + SolarMutexGuard aGuard; + + InvalidateFocus_(); +} + +// #i27301# - use new type definition for <_nStates> +void SwAccessibleContext::InvalidateStates( AccessibleStates _nStates ) +{ + if( !GetMap() ) + return; + + SwViewShell *pVSh = GetMap()->GetShell(); + if( pVSh ) + { + if( _nStates & AccessibleStates::EDITABLE ) + { + bool bIsOldEditableState; + bool bIsNewEditableState = IsEditable( pVSh ); + { + std::scoped_lock aGuard( m_Mutex ); + bIsOldEditableState = m_isEditableState; + m_isEditableState = bIsNewEditableState; + } + + if( bIsOldEditableState != bIsNewEditableState ) + FireStateChangedEvent( AccessibleStateType::EDITABLE, + bIsNewEditableState ); + } + if( _nStates & AccessibleStates::OPAQUE ) + { + bool bIsOldOpaqueState; + bool bIsNewOpaqueState = IsOpaque( pVSh ); + { + std::scoped_lock aGuard( m_Mutex ); + bIsOldOpaqueState = m_isOpaqueState; + m_isOpaqueState = bIsNewOpaqueState; + } + + if( bIsOldOpaqueState != bIsNewOpaqueState ) + FireStateChangedEvent( AccessibleStateType::OPAQUE, + bIsNewOpaqueState ); + } + } + + InvalidateChildrenStates( GetFrame(), _nStates ); +} + +void SwAccessibleContext::InvalidateRelation( sal_uInt16 nType ) +{ + AccessibleEventObject aEvent; + aEvent.EventId = nType; + + FireAccessibleEvent( aEvent ); +} + +/** #i27301# - text selection has changed */ +void SwAccessibleContext::InvalidateTextSelection() +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TEXT_SELECTION_CHANGED; + + FireAccessibleEvent( aEvent ); +} + +/** #i88069# - attributes has changed */ +void SwAccessibleContext::InvalidateAttr() +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TEXT_ATTRIBUTE_CHANGED; + + FireAccessibleEvent( aEvent ); +} + +bool SwAccessibleContext::HasCursor() +{ + return false; +} + +bool SwAccessibleContext::Select( SwPaM *pPaM, SdrObject *pObj, + bool bAdd ) +{ + SwCursorShell* pCursorShell = GetCursorShell(); + if( !pCursorShell ) + return false; + + SwFEShell* pFEShell = dynamic_cast<SwFEShell*>(pCursorShell); + // Get rid of activated OLE object + if( pFEShell ) + pFEShell->FinishOLEObj(); + + SwWrtShell* pWrtShell = dynamic_cast<SwWrtShell*>(pCursorShell); + + bool bRet = false; + if( pObj ) + { + if( pFEShell ) + { + sal_uInt8 nFlags = bAdd ? SW_ADD_SELECT : 0; + pFEShell->SelectObj( Point(), nFlags, pObj ); + bRet = true; + } + } + else if( pPaM ) + { + // Get rid of frame selection. If there is one, make text cursor + // visible again. + bool bCallShowCursor = false; + if( pFEShell && (pFEShell->IsFrameSelected() || + pFEShell->IsObjSelected()) ) + { + Point aPt( LONG_MIN, LONG_MIN ); + pFEShell->SelectObj( aPt ); + bCallShowCursor = true; + } + pCursorShell->KillPams(); + if( pWrtShell && pPaM->HasMark() ) + // We have to do this or SwWrtShell can't figure out that it needs + // to kill the selection later, when the user moves the cursor. + pWrtShell->SttSelect(); + pCursorShell->SetSelection( *pPaM ); + if( pPaM->HasMark() && *pPaM->GetPoint() == *pPaM->GetMark()) + // Setting a "Selection" that starts and ends at the same spot + // should remove the selection rather than create an empty one, so + // that we get defined behavior if accessibility sets the cursor + // later. + pCursorShell->ClearMark(); + if( bCallShowCursor ) + pCursorShell->ShowCursor(); + bRet = true; + } + + return bRet; +} + +OUString SwAccessibleContext::GetResource(TranslateId pResId, + const OUString *pArg1, + const OUString *pArg2) +{ + OUString sStr = SwResId(pResId); + + if( pArg1 ) + { + sStr = sStr.replaceFirst( "$(ARG1)", *pArg1 ); + } + if( pArg2 ) + { + sStr = sStr.replaceFirst( "$(ARG2)", *pArg2 ); + } + + return sStr; +} + +void SwAccessibleContext::RemoveFrameFromAccessibleMap() +{ + assert(m_refCount > 0); // must be alive to do this without using m_wMap + if (m_isRegisteredAtAccessibleMap && GetFrame() && GetMap()) + GetMap()->RemoveContext( GetFrame() ); +} + +bool SwAccessibleContext::HasAdditionalAccessibleChildren() +{ + bool bRet( false ); + + if ( GetFrame()->IsTextFrame() ) + { + SwPostItMgr* pPostItMgr = GetMap()->GetShell()->GetPostItMgr(); + if ( pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ) + { + bRet = pPostItMgr->HasFrameConnectedSidebarWins( *(GetFrame()) ); + } + } + + return bRet; +} + +/** #i88070# - get additional accessible child by index */ +vcl::Window* SwAccessibleContext::GetAdditionalAccessibleChild( const sal_Int32 nIndex ) +{ + vcl::Window* pAdditionalAccessibleChild( nullptr ); + + if ( GetFrame()->IsTextFrame() ) + { + SwPostItMgr* pPostItMgr = GetMap()->GetShell()->GetPostItMgr(); + if ( pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ) + { + pAdditionalAccessibleChild = + pPostItMgr->GetSidebarWinForFrameByIndex( *(GetFrame()), nIndex ); + } + } + + return pAdditionalAccessibleChild; +} + +/** #i88070# - get all additional accessible children */ +void SwAccessibleContext::GetAdditionalAccessibleChildren( std::vector< vcl::Window* >* pChildren ) +{ + if ( GetFrame()->IsTextFrame() ) + { + SwPostItMgr* pPostItMgr = GetMap()->GetShell()->GetPostItMgr(); + if ( pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ) + { + pPostItMgr->GetAllSidebarWinForFrame( *(GetFrame()), pChildren ); + } + } +} + +bool SwAccessibleContext::SetSelectedState(bool const bSelected) +{ + if (m_isSelectedInDoc != bSelected) + { + m_isSelectedInDoc = bSelected; + FireStateChangedEvent( AccessibleStateType::SELECTED, bSelected ); + return true; + } + return false; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acccontext.hxx b/sw/source/core/access/acccontext.hxx new file mode 100644 index 000000000..4faf8e237 --- /dev/null +++ b/sw/source/core/access/acccontext.hxx @@ -0,0 +1,362 @@ +/* -*- 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 . + */ + +#pragma once + +#include "accframe.hxx" +#include <accmap.hxx> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleContext3.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/implbase.hxx> +#include <unotools/resmgr.hxx> + +#include <memory> +#include <mutex> + +namespace vcl { class Window; } +class SwCursorShell; +class SdrObject; +class SwPaM; +namespace utl { + class AccessibleStateSetHelper; +} +namespace accessibility { + class AccessibleShape; +} + +inline constexpr OUStringLiteral sAccessibleServiceName = u"com.sun.star.accessibility.Accessible"; + +class SwAccessibleContext : + public ::cppu::WeakImplHelper< + css::accessibility::XAccessible, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleContext3, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleEventBroadcaster, + css::lang::XServiceInfo + >, + public SwAccessibleFrame +{ + // The implements for the XAccessibleSelection interface has been + // 'externalized' and wants access to the protected members like + // GetMap, GetChild, GetParent, and GetFrame. + friend class SwAccessibleSelectionHelper; +#if OSL_DEBUG_LEVEL > 0 + friend class SwAccessibleMap; +#endif + +protected: + mutable std::mutex m_Mutex; + +private: + OUString m_sName; // immutable outside constructor + + // The parent if it has been retrieved. This is always an + // SwAccessibleContext. (protected by Mutex) + css::uno::WeakReference < + css::accessibility::XAccessible > m_xWeakParent; + + SwAccessibleMap *m_pMap; // must be protected by solar mutex + /// note: the m_pMap is guaranteed to be valid until we hit the + /// dtor ~SwAccessibleContext, then m_wMap must be checked if it's still + /// alive, after locking SolarMutex (alternatively, Dispose clears m_pMap) + std::weak_ptr<SwAccessibleMap> m_wMap; + + sal_uInt32 m_nClientId; // client id in the AccessibleEventNotifier queue + sal_Int16 m_nRole; // immutable outside constructor + + // The current states (protected by mutex) + bool m_isShowingState : 1; + bool m_isEditableState : 1; + bool m_isOpaqueState : 1; + bool m_isDefuncState : 1; + + // Are we currently disposing that object (protected by solar mutex)? + bool m_isDisposing : 1; + + // #i85634# - boolean, indicating if the accessible context is + // in general registered at the accessible map. + bool m_isRegisteredAtAccessibleMap; + + void InitStates(); + +protected: + void SetName( const OUString& rName ) { m_sName = rName; } + sal_Int16 GetRole() const + { + return m_nRole; + } + //This flag is used to mark the object's selected state. + bool m_isSelectedInDoc; + void SetParent( SwAccessibleContext *pParent ); + css::uno::Reference< css::accessibility::XAccessible> GetWeakParent() const; + + bool IsDisposing() const { return m_isDisposing; } + + vcl::Window *GetWindow(); + SwAccessibleMap *GetMap() { return m_pMap; } + const SwAccessibleMap *GetMap() const { return m_pMap; } + + /** convenience method to get the SwViewShell through accessibility map */ + SwViewShell* GetShell() + { + return GetMap()->GetShell(); + } + const SwViewShell* GetShell() const + { + return GetMap()->GetShell(); + } + + /** convenience method to get SwCursorShell through accessibility map + * @returns SwCursorShell, or NULL if none is found */ + SwCursorShell* GetCursorShell(); + const SwCursorShell* GetCursorShell() const; + + // Notify all children that the visible area has changed. + // The SwFrame might belong to the current object or to any other child or + // grandchild. + void ChildrenScrolled( const SwFrame *pFrame, const SwRect& rOldVisArea ); + + // The context's showing state changed. May only be called for context that + // exist even if they aren't visible. + void Scrolled( const SwRect& rOldVisArea ); + + // A child has been moved while setting the visible area + void ScrolledWithin( const SwRect& rOldVisArea ); + + // The has been added while setting the visible area + void ScrolledIn(); + + // The context has to be removed while setting the visible area + void ScrolledOut( const SwRect& rOldVisArea ); + + // Invalidate the states of all children of the specified SwFrame. The + // SwFrame might belong the current object or to any child or grandchild! + // #i27301# - use new type definition for <_nStates> + void InvalidateChildrenStates( const SwFrame* _pFrame, + AccessibleStates _nStates ); + + // Dispose children of the specified SwFrame. The SwFrame might belong to + // the current object or to any other child or grandchild. + void DisposeChildren(const SwFrame *pFrame, + bool bRecursive, bool bCanSkipInvisible); + + void DisposeShape( const SdrObject *pObj, + ::accessibility::AccessibleShape *pAccImpl ); + void ScrolledInShape( ::accessibility::AccessibleShape *pAccImpl ); + + virtual void InvalidateContent_( bool bVisibleDataFired ); + + virtual void InvalidateCursorPos_(); + virtual void InvalidateFocus_(); + +public: + void FireAccessibleEvent( css::accessibility::AccessibleEventObject& rEvent ); + +protected: + // broadcast visual data event + void FireVisibleDataEvent(); + + // broadcast state change event + void FireStateChangedEvent( sal_Int16 nState, bool bNewState ); + + // Set states for getAccessibleStateSet. + // This base class sets DEFUNC(0/1), EDITABLE(0/1), ENABLED(1), + // SHOWING(0/1), OPAQUE(0/1) and VISIBLE(1). + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ); + + bool IsEditableState(); + + /// @throws css::uno::RuntimeException + css::awt::Rectangle + getBoundsImpl(bool bRelative); + + // #i85634# + void NotRegisteredAtAccessibleMap() + { + m_isRegisteredAtAccessibleMap = false; + } + void RemoveFrameFromAccessibleMap(); + + void ThrowIfDisposed(); + + virtual ~SwAccessibleContext() override; + + // Return a reference to the parent. + css::uno::Reference< css::accessibility::XAccessible> + getAccessibleParentImpl(); + +public: + SwAccessibleContext( std::shared_ptr<SwAccessibleMap> const& pMap, + sal_Int16 nRole, const SwFrame *pFrame ); + + // XAccessible + + // Return the XAccessibleContext. + virtual css::uno::Reference< css::accessibility::XAccessibleContext> SAL_CALL + getAccessibleContext() override; + + // XAccessibleContext + + // Return the number of currently visible children. + virtual sal_Int32 SAL_CALL getAccessibleChildCount() override; + + // Return the specified child or NULL if index is invalid. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleChild (sal_Int32 nIndex) override; + + virtual css::uno::Sequence<css::uno::Reference< css::accessibility::XAccessible>> SAL_CALL + getAccessibleChildren() override; + + // Return a reference to the parent. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleParent() override; + + // Return this objects index among the parents children. + virtual sal_Int32 SAL_CALL + getAccessibleIndexInParent() override; + + // Return this object's role. + virtual sal_Int16 SAL_CALL + getAccessibleRole() override; + + // getAccessibleDescription() is abstract + + // Return the object's current name. + virtual OUString SAL_CALL + getAccessibleName() override; + + // Return NULL to indicate that an empty relation set. + virtual css::uno::Reference< + css::accessibility::XAccessibleRelationSet> SAL_CALL + getAccessibleRelationSet() override; + + // Return the set of current states. + virtual css::uno::Reference< + css::accessibility::XAccessibleStateSet> SAL_CALL + getAccessibleStateSet() override; + + /** Return the parents locale or throw exception if this object has no + parent yet/anymore. */ + virtual css::lang::Locale SAL_CALL + getLocale() override; + + // XAccessibleEventBroadcaster + + virtual void SAL_CALL addAccessibleEventListener( + const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( + const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( + const css::awt::Point& aPoint ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( + const css::awt::Point& aPoint ) override; + + virtual css::awt::Rectangle SAL_CALL getBounds() override; + + virtual css::awt::Point SAL_CALL getLocation() override; + + virtual css::awt::Point SAL_CALL getLocationOnScreen() override; + + virtual css::awt::Size SAL_CALL getSize() override; + + virtual void SAL_CALL grabFocus() override; + + virtual sal_Int32 SAL_CALL getForeground() override; + virtual sal_Int32 SAL_CALL getBackground() override; + + // XServiceInfo + + // getImplementationName() and getSupportedServiceNames are abstract + + /** Return whether the specified service is supported by this class. */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // thread safe C++ interface + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true); + + // The child object is not visible any longer and should be destroyed + virtual void DisposeChild(const sw::access::SwAccessibleChild& rFrameOrObj, bool bRecursive, bool bCanSkipInvisible); + + // The object has been moved by the layout + virtual void InvalidatePosOrSize( const SwRect& rFrame ); + + // The child object has been moved by the layout + virtual void InvalidateChildPosOrSize( const sw::access::SwAccessibleChild& rFrameOrObj, + const SwRect& rFrame ); + + // The content may have changed (but it hasn't to have changed) + void InvalidateContent(); + + // The caretPos has changed + void InvalidateCursorPos(); + + // The Focus state has changed + void InvalidateFocus(); + + // Check states + // #i27301# - use new type definition for <_nStates> + void InvalidateStates( AccessibleStates _nStates ); + + // the XAccessibleRelationSet may have changed + void InvalidateRelation( sal_uInt16 nType ); + + void InvalidateTextSelection(); // #i27301# - text selection has changed + void InvalidateAttr(); // #i88069# - attributes has changed + + bool HasAdditionalAccessibleChildren(); + + // #i88070# - get additional child by index + vcl::Window* GetAdditionalAccessibleChild( const sal_Int32 nIndex ); + + // #i88070# - get all additional accessible children + void GetAdditionalAccessibleChildren( std::vector< vcl::Window* >* pChildren ); + + const OUString& GetName() const { return m_sName; } + + virtual bool HasCursor(); // required by map to remember that object + + bool Select( SwPaM *pPaM, SdrObject *pObj, bool bAdd ); + bool Select( SwPaM& rPaM ) + { + return Select( &rPaM, nullptr, false ); + } + bool Select( SdrObject *pObj, bool bAdd ) + { + return Select( nullptr, pObj, bAdd ); + } + + //This method is used to update the selected state and fire the selected state changed event. + virtual bool SetSelectedState(bool bSelected); + bool IsSelectedInDoc() const { return m_isSelectedInDoc; } + + static OUString GetResource(TranslateId pResId, + const OUString *pArg1 = nullptr, + const OUString *pArg2 = nullptr); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accdoc.cxx b/sw/source/core/access/accdoc.cxx new file mode 100644 index 000000000..4eb719242 --- /dev/null +++ b/sw/source/core/access/accdoc.cxx @@ -0,0 +1,716 @@ +/* -*- 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 . + */ + +#include <vcl/window.hxx> +#include <rootfrm.hxx> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <unotools/accessiblestatesethelper.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <vcl/svapp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <viewsh.hxx> +#include <doc.hxx> +#include <accmap.hxx> +#include "accdoc.hxx" +#include <strings.hrc> +#include <pagefrm.hxx> + +#include <swatrset.hxx> +#include <docsh.hxx> +#include <crsrsh.hxx> +#include <fesh.hxx> +#include <fmtclds.hxx> +#include <flyfrm.hxx> +#include <txtfrm.hxx> +#include <sectfrm.hxx> +#include <section.hxx> +#include <swmodule.hxx> +#include <svtools/colorcfg.hxx> + +#include <fmtanchr.hxx> +#include <viewimp.hxx> +#include <dview.hxx> +#include <dcontact.hxx> +#include <svx/svdmark.hxx> +constexpr OUStringLiteral sServiceName = u"com.sun.star.text.AccessibleTextDocumentView"; +constexpr OUStringLiteral sImplementationName = u"com.sun.star.comp.Writer.SwAccessibleDocumentView"; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +using lang::IndexOutOfBoundsException; + +// SwAccessibleDocumentBase: base class for SwAccessibleDocument and +// SwAccessiblePreview + +SwAccessibleDocumentBase::SwAccessibleDocumentBase( + std::shared_ptr<SwAccessibleMap> const& pMap) + : SwAccessibleContext(pMap, AccessibleRole::DOCUMENT_TEXT, + pMap->GetShell()->GetLayout()) + , mxParent(pMap->GetShell()->GetWin()->GetAccessibleParentWindow()->GetAccessible()) + , mpChildWin(nullptr) +{ +} + +SwAccessibleDocumentBase::~SwAccessibleDocumentBase() +{ +} + +void SwAccessibleDocumentBase::SetVisArea() +{ + SolarMutexGuard aGuard; + + SwRect aOldVisArea( GetVisArea() ); + const SwRect& rNewVisArea = GetMap()->GetVisArea(); + if( aOldVisArea != rNewVisArea ) + { + SwAccessibleFrame::SetVisArea( GetMap()->GetVisArea() ); + // #i58139# - showing state of document view needs also be updated. + // Thus, call method <Scrolled(..)> instead of <ChildrenScrolled(..)> + // ChildrenScrolled( GetFrame(), aOldVisArea ); + Scrolled( aOldVisArea ); + } +} + +void SwAccessibleDocumentBase::AddChild( vcl::Window *pWin, bool bFireEvent ) +{ + SolarMutexGuard aGuard; + + OSL_ENSURE( !mpChildWin, "only one child window is supported" ); + if( !mpChildWin ) + { + mpChildWin = pWin; + + if( bFireEvent ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= mpChildWin->GetAccessible(); + FireAccessibleEvent( aEvent ); + } + } +} + +void SwAccessibleDocumentBase::RemoveChild( vcl::Window *pWin ) +{ + SolarMutexGuard aGuard; + + OSL_ENSURE( !mpChildWin || pWin == mpChildWin, "invalid child window to remove" ); + if( mpChildWin && pWin == mpChildWin ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.OldValue <<= mpChildWin->GetAccessible(); + FireAccessibleEvent( aEvent ); + + mpChildWin = nullptr; + } +} + +sal_Int32 SAL_CALL SwAccessibleDocumentBase::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + + // ThrowIfDisposed is called by parent + + sal_Int32 nChildren = SwAccessibleContext::getAccessibleChildCount(); + if( !IsDisposing() && mpChildWin ) + nChildren++; + + return nChildren; +} + +uno::Reference< XAccessible> SAL_CALL + SwAccessibleDocumentBase::getAccessibleChild( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + if( mpChildWin ) + { + ThrowIfDisposed(); + + if ( nIndex == GetChildCount( *(GetMap()) ) ) + { + return mpChildWin->GetAccessible(); + } + } + + return SwAccessibleContext::getAccessibleChild( nIndex ); +} + +uno::Reference< XAccessible> SAL_CALL SwAccessibleDocumentBase::getAccessibleParent() +{ + return mxParent; +} + +sal_Int32 SAL_CALL SwAccessibleDocumentBase::getAccessibleIndexInParent() +{ + SolarMutexGuard aGuard; + + uno::Reference < XAccessibleContext > xAcc( mxParent->getAccessibleContext() ); + uno::Reference < XAccessible > xThis( this ); + sal_Int32 nCount = xAcc->getAccessibleChildCount(); + + for( sal_Int32 i=0; i < nCount; i++ ) + { + try + { + if( xAcc->getAccessibleChild( i ) == xThis ) + return i; + } + catch(const css::lang::IndexOutOfBoundsException &) + { + return -1; + } + } + return -1; +} + +OUString SAL_CALL SwAccessibleDocumentBase::getAccessibleDescription() +{ + return GetResource( STR_ACCESS_DOC_DESC ); +} + +OUString SAL_CALL SwAccessibleDocumentBase::getAccessibleName() +{ + SolarMutexGuard g; + + OUString sAccName = GetResource( STR_ACCESS_DOC_WORDPROCESSING ); + SwDoc *pDoc = GetMap() ? GetShell()->GetDoc() : nullptr; + if ( pDoc ) + { + OUString sFileName = pDoc->getDocAccTitle(); + if ( sFileName.isEmpty() ) + { + SwDocShell* pDocSh = pDoc->GetDocShell(); + if ( pDocSh ) + { + sFileName = pDocSh->GetTitle( SFX_TITLE_APINAME ); + } + } + + if ( !sFileName.isEmpty() ) + { + sAccName = sFileName + " - " + sAccName; + } + } + + return sAccName; +} + +awt::Rectangle SAL_CALL SwAccessibleDocumentBase::getBounds() +{ + try + { + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + tools::Rectangle aPixBounds( pWin->GetWindowExtentsRelative( pWin->GetAccessibleParentWindow() ) ); + awt::Rectangle aBox( aPixBounds.Left(), aPixBounds.Top(), + aPixBounds.GetWidth(), aPixBounds.GetHeight() ); + + return aBox; + } + catch(const css::lang::IndexOutOfBoundsException &) + { + return awt::Rectangle(); + } +} + +awt::Point SAL_CALL SwAccessibleDocumentBase::getLocation() +{ + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + Point aPixPos( pWin->GetWindowExtentsRelative( pWin->GetAccessibleParentWindow() ).TopLeft() ); + awt::Point aLoc( aPixPos.getX(), aPixPos.getY() ); + + return aLoc; +} + +css::awt::Point SAL_CALL SwAccessibleDocumentBase::getLocationOnScreen() +{ + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + Point aPixPos( pWin->GetWindowExtentsRelative( nullptr ).TopLeft() ); + awt::Point aLoc( aPixPos.getX(), aPixPos.getY() ); + + return aLoc; +} + +css::awt::Size SAL_CALL SwAccessibleDocumentBase::getSize() +{ + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + Size aPixSize( pWin->GetWindowExtentsRelative( nullptr ).GetSize() ); + awt::Size aSize( aPixSize.Width(), aPixSize.Height() ); + + return aSize; +} + +sal_Bool SAL_CALL SwAccessibleDocumentBase::containsPoint( + const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + tools::Rectangle aPixBounds( pWin->GetWindowExtentsRelative( nullptr ) ); + aPixBounds.Move(-aPixBounds.Left(), -aPixBounds.Top()); + + Point aPixPoint( aPoint.X, aPoint.Y ); + return aPixBounds.Contains( aPixPoint ); +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleDocumentBase::getAccessibleAtPoint( + const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + + if( mpChildWin ) + { + ThrowIfDisposed(); + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + if (pWin->isDisposed()) // tdf#147967 + return nullptr; + + Point aPixPoint( aPoint.X, aPoint.Y ); // px rel to window + if( mpChildWin->GetWindowExtentsRelative( pWin ).Contains( aPixPoint ) ) + return mpChildWin->GetAccessible(); + } + + return SwAccessibleContext::getAccessibleAtPoint( aPoint ); +} + +// SwAccessibleDocument + +void SwAccessibleDocument::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + // MULTISELECTABLE + rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE ); + rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS ); +} + +SwAccessibleDocument::SwAccessibleDocument( + std::shared_ptr<SwAccessibleMap> const& pInitMap) + : SwAccessibleDocumentBase(pInitMap) + , maSelectionHelper(*this) +{ + SetName(pInitMap->GetDocName()); + vcl::Window *pWin = pInitMap->GetShell()->GetWin(); + if( pWin ) + { + pWin->AddChildEventListener( LINK( this, SwAccessibleDocument, WindowChildEventListener )); + sal_uInt16 nCount = pWin->GetChildCount(); + for( sal_uInt16 i=0; i < nCount; i++ ) + { + vcl::Window* pChildWin = pWin->GetChild( i ); + if( pChildWin && + AccessibleRole::EMBEDDED_OBJECT == pChildWin->GetAccessibleRole() ) + AddChild( pChildWin, false ); + } + } +} + +SwAccessibleDocument::~SwAccessibleDocument() +{ + vcl::Window *pWin = GetMap() ? GetMap()->GetShell()->GetWin() : nullptr; + if( pWin ) + pWin->RemoveChildEventListener( LINK( this, SwAccessibleDocument, WindowChildEventListener )); +} + +void SwAccessibleDocument::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + OSL_ENSURE( GetFrame() && GetMap(), "already disposed" ); + + vcl::Window *pWin = GetMap() ? GetMap()->GetShell()->GetWin() : nullptr; + if( pWin ) + pWin->RemoveChildEventListener( LINK( this, SwAccessibleDocument, WindowChildEventListener )); + SwAccessibleContext::Dispose(bRecursive, bCanSkipInvisible); +} + +IMPL_LINK( SwAccessibleDocument, WindowChildEventListener, VclWindowEvent&, rEvent, void ) +{ + OSL_ENSURE( rEvent.GetWindow(), "Window???" ); + switch ( rEvent.GetId() ) + { + case VclEventId::WindowShow: // send create on show for direct accessible children + { + vcl::Window* pChildWin = static_cast< vcl::Window* >( rEvent.GetData() ); + if( pChildWin && AccessibleRole::EMBEDDED_OBJECT == pChildWin->GetAccessibleRole() ) + { + AddChild( pChildWin ); + } + } + break; + case VclEventId::WindowHide: // send destroy on hide for direct accessible children + { + vcl::Window* pChildWin = static_cast< vcl::Window* >( rEvent.GetData() ); + if( pChildWin && AccessibleRole::EMBEDDED_OBJECT == pChildWin->GetAccessibleRole() ) + { + RemoveChild( pChildWin ); + } + } + break; + case VclEventId::ObjectDying: // send destroy on hide for direct accessible children + { + vcl::Window* pChildWin = rEvent.GetWindow(); + if( pChildWin && AccessibleRole::EMBEDDED_OBJECT == pChildWin->GetAccessibleRole() ) + { + RemoveChild( pChildWin ); + } + } + break; + default: break; + } +} + +OUString SAL_CALL SwAccessibleDocument::getImplementationName() +{ + return sImplementationName; +} + +sal_Bool SAL_CALL SwAccessibleDocument::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleDocument::getSupportedServiceNames() +{ + return { sServiceName, sAccessibleServiceName }; +} + +// XInterface + +uno::Any SwAccessibleDocument::queryInterface( + const uno::Type& rType ) +{ + uno::Any aRet; + if ( rType == cppu::UnoType<XAccessibleSelection>::get() ) + { + uno::Reference<XAccessibleSelection> aSelect = this; + aRet <<= aSelect; + } + else if ( rType == cppu::UnoType<XAccessibleExtendedAttributes>::get()) + { + uno::Reference<XAccessibleExtendedAttributes> aAttribute = this; + aRet <<= aAttribute; + } + else + aRet = SwAccessibleContext::queryInterface( rType ); + return aRet; +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SwAccessibleDocument::getTypes() +{ + return cppu::OTypeCollection( + cppu::UnoType<XAccessibleSelection>::get(), + SwAccessibleDocumentBase::getTypes() ).getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleDocument::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XAccessibleSelection + +void SwAccessibleDocument::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + maSelectionHelper.selectAccessibleChild(nChildIndex); +} + +sal_Bool SwAccessibleDocument::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + return maSelectionHelper.isAccessibleChildSelected(nChildIndex); +} + +void SwAccessibleDocument::clearAccessibleSelection( ) +{ +} + +void SwAccessibleDocument::selectAllAccessibleChildren( ) +{ + maSelectionHelper.selectAllAccessibleChildren(); +} + +sal_Int32 SwAccessibleDocument::getSelectedAccessibleChildCount( ) +{ + return maSelectionHelper.getSelectedAccessibleChildCount(); +} + +uno::Reference<XAccessible> SwAccessibleDocument::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + return maSelectionHelper.getSelectedAccessibleChild(nSelectedChildIndex); +} + +// index has to be treated as global child index. +void SwAccessibleDocument::deselectAccessibleChild( + sal_Int32 nChildIndex ) +{ + maSelectionHelper.deselectAccessibleChild( nChildIndex ); +} + +uno::Any SAL_CALL SwAccessibleDocument::getExtendedAttributes() +{ + SolarMutexGuard g; + + uno::Any anyAttribute; + SwDoc *pDoc = GetMap() ? GetShell()->GetDoc() : nullptr; + + if (!pDoc) + return anyAttribute; + SwCursorShell* pCursorShell = GetCursorShell(); + if( !pCursorShell ) + return anyAttribute; + + SwFEShell* pFEShell = dynamic_cast<SwFEShell*>(pCursorShell); + if( pFEShell ) + { + OUString sDisplay; + sal_uInt16 nPage, nLogPage; + pFEShell->GetPageNumber(-1,true,nPage,nLogPage,sDisplay); + + OUString sValue = "page-name:" + sDisplay + + ";page-number:" + + OUString::number( nPage ) + + ";total-pages:" + + OUString::number( pCursorShell->GetPageCnt() ) + ";"; + + SwContentFrame* pCurrFrame = pCursorShell->GetCurrFrame(); + SwPageFrame* pCurrPage=static_cast<SwFrame*>(pCurrFrame)->FindPageFrame(); + sal_uLong nLineNum = 0; + SwTextFrame* pTextFrame = nullptr; + SwTextFrame* pCurrTextFrame = nullptr; + pTextFrame = static_cast< SwTextFrame* >(pCurrPage->ContainsContent()); + if (pCurrFrame->IsInFly())//such as, graphic,chart + { + SwFlyFrame *pFlyFrame = pCurrFrame->FindFlyFrame(); + const SwFormatAnchor& rAnchor = pFlyFrame->GetFormat()->GetAnchor(); + RndStdIds eAnchorId = rAnchor.GetAnchorId(); + if(eAnchorId == RndStdIds::FLY_AS_CHAR) + { + const SwFrame *pSwFrame = pFlyFrame->GetAnchorFrame(); + if(pSwFrame->IsTextFrame()) + pCurrTextFrame = const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pSwFrame)); + } + } + else + { + assert(dynamic_cast<SwTextFrame*>(pCurrFrame)); + pCurrTextFrame = static_cast<SwTextFrame* >(pCurrFrame); + } + //check whether the text frame where the Graph/OLE/Frame anchored is in the Header/Footer + SwFrame* pFrame = pCurrTextFrame; + while ( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() ) + pFrame = pFrame->GetUpper(); + if ( pFrame ) + pCurrTextFrame = nullptr; + //check shape + if(pCursorShell->Imp()->GetDrawView()) + { + const SdrMarkList &rMrkList = pCursorShell->Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark(i)->GetMarkedSdrObj(); + SwFrameFormat* pFormat = static_cast<SwDrawContact*>(pObj->GetUserCall())->GetFormat(); + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if( RndStdIds::FLY_AS_CHAR != rAnchor.GetAnchorId() ) + pCurrTextFrame = nullptr; + } + } + //calculate line number + if (pCurrTextFrame && pTextFrame) + { + if (!(pCurrTextFrame->IsInTab() || pCurrTextFrame->IsInFootnote())) + { + while( pTextFrame && pTextFrame != pCurrTextFrame ) + { + //check header/footer + pFrame = pTextFrame; + while ( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() ) + pFrame = pFrame->GetUpper(); + if ( pFrame ) + { + pTextFrame = static_cast< SwTextFrame*>(pTextFrame->GetNextContentFrame()); + continue; + } + if (!(pTextFrame->IsInTab() || pTextFrame->IsInFootnote() || pTextFrame->IsInFly())) + nLineNum += pTextFrame->GetThisLines(); + pTextFrame = static_cast< SwTextFrame* >(pTextFrame ->GetNextContentFrame()); + } + SwPaM* pCaret = pCursorShell->GetCursor(); + if (!pCurrTextFrame->IsEmpty() && pCaret) + { + assert(pCurrTextFrame->IsTextFrame()); + const SwPosition* pPoint = nullptr; + if (pCurrTextFrame->IsInFly()) + { + SwFlyFrame *pFlyFrame = pCurrTextFrame->FindFlyFrame(); + const SwFormatAnchor& rAnchor = pFlyFrame->GetFormat()->GetAnchor(); + pPoint = rAnchor.GetContentAnchor(); + SwContentNode *const pNode(pPoint->nNode.GetNode().GetContentNode()); + pCurrTextFrame = pNode + ? static_cast<SwTextFrame*>(pNode->getLayoutFrame( + pCurrTextFrame->getRootFrame(), pPoint)) + : nullptr; + } + else + pPoint = pCaret->GetPoint(); + if (pCurrTextFrame) + { + TextFrameIndex const nActPos(pCurrTextFrame->MapModelToViewPos(*pPoint)); + nLineNum += pCurrTextFrame->GetLineCount( nActPos ); + } + } + else + ++nLineNum; + } + } + + sValue += "line-number:" + OUString::number( nLineNum ) + ";"; + + SwFrame* pCurrCol=static_cast<SwFrame*>(pCurrFrame)->FindColFrame(); + + sValue += "column-number:"; + + int nCurrCol = 1; + if(pCurrCol!=nullptr) + { + //SwLayoutFrame* pParent = pCurrCol->GetUpper(); + SwFrame* pCurrPageCol=static_cast<SwFrame*>(pCurrFrame)->FindColFrame(); + while(pCurrPageCol && pCurrPageCol->GetUpper() && pCurrPageCol->GetUpper()->IsPageFrame()) + { + pCurrPageCol = pCurrPageCol->GetUpper(); + } + + SwLayoutFrame* pParent = pCurrPageCol->GetUpper(); + + if(pParent!=nullptr) + { + SwFrame* pCol = pParent->Lower(); + while(pCol&&(pCol!=pCurrPageCol)) + { + pCol = pCol->GetNext(); + ++nCurrCol; + } + } + } + sValue += OUString::number( nCurrCol ) + ";"; + + const SwFormatCol &rFormatCol=pCurrPage->GetAttrSet()->GetCol(); + sal_uInt16 nColCount=rFormatCol.GetNumCols(); + nColCount = nColCount>0?nColCount:1; + sValue += "total-columns:" + OUString::number( nColCount ) + ";"; + + SwSectionFrame* pCurrSctFrame=static_cast<SwFrame*>(pCurrFrame)->FindSctFrame(); + if(pCurrSctFrame!=nullptr && pCurrSctFrame->GetSection()!=nullptr ) + { + OUString sectionName = pCurrSctFrame->GetSection()->GetSectionName(); + + sectionName = sectionName.replaceFirst( "\\" , "\\\\" ); + sectionName = sectionName.replaceFirst( "=" , "\\=" ); + sectionName = sectionName.replaceFirst( ";" , "\\;" ); + sectionName = sectionName.replaceFirst( "," , "\\," ); + sectionName = sectionName.replaceFirst( ":" , "\\:" ); + + sValue += "section-name:" + sectionName + ";"; + + //section-columns-number + + nCurrCol = 1; + + if(pCurrCol!=nullptr) + { + SwLayoutFrame* pParent = pCurrCol->GetUpper(); + if(pParent!=nullptr) + { + SwFrame* pCol = pParent->Lower(); + while(pCol&&(pCol!=pCurrCol)) + { + pCol = pCol->GetNext(); + nCurrCol +=1; + } + } + } + sValue += "section-columns-number:" + + OUString::number( nCurrCol ) + ";"; + + //section-total-columns + const SwFormatCol &rFormatSctCol=pCurrSctFrame->GetAttrSet()->GetCol(); + sal_uInt16 nSctColCount=rFormatSctCol.GetNumCols(); + nSctColCount = nSctColCount>0?nSctColCount:1; + sValue += "section-total-columns:" + + OUString::number( nSctColCount ) + ";"; + } + + anyAttribute <<= sValue; + } + return anyAttribute; +} + +sal_Int32 SAL_CALL SwAccessibleDocument::getBackground() +{ + SolarMutexGuard aGuard; + return sal_Int32(SW_MOD()->GetColorConfig().GetColorValue( ::svtools::DOCCOLOR ).nColor); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accdoc.hxx b/sw/source/core/access/accdoc.hxx new file mode 100644 index 000000000..6f16cc585 --- /dev/null +++ b/sw/source/core/access/accdoc.hxx @@ -0,0 +1,173 @@ +/* -*- 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 . + */ + +#pragma once + +#include "acccontext.hxx" +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp> +#include "accselectionhelper.hxx" +#include <vcl/window.hxx> + +// base class for SwAccessibleDocument (in this same header file) and +// SwAccessiblePreview +class SwAccessibleDocumentBase : public SwAccessibleContext +{ + css::uno::Reference< css::accessibility::XAccessible> mxParent; + + VclPtr<vcl::Window> mpChildWin; // protected by solar mutex + + using SwAccessibleFrame::SetVisArea; + +protected: + virtual ~SwAccessibleDocumentBase() override; + +public: + SwAccessibleDocumentBase(std::shared_ptr<SwAccessibleMap> const& pInitMap); + + void SetVisArea(); + + void AddChild( vcl::Window *pWin, bool bFireEvent = true ); + void RemoveChild( vcl::Window *pWin ); + + // XAccessibleContext + + // Return the number of currently visible children. + virtual sal_Int32 SAL_CALL getAccessibleChildCount() override; + + // Return the specified child or NULL if index is invalid. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleChild (sal_Int32 nIndex) override; + + // Return a reference to the parent. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleParent() override; + + // Return this objects index among the parents children. + virtual sal_Int32 SAL_CALL + getAccessibleIndexInParent() override; + + // Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + virtual OUString SAL_CALL getAccessibleName() override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( + const css::awt::Point& aPoint ) override; + + virtual css::uno::Reference< + css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( + const css::awt::Point& aPoint ) override; + + virtual css::awt::Rectangle SAL_CALL getBounds() override; + + virtual css::awt::Point SAL_CALL getLocation() override; + + virtual css::awt::Point SAL_CALL getLocationOnScreen() override; + + virtual css::awt::Size SAL_CALL getSize() override; +}; + +/** + * access to an accessible Writer document + */ +class SwAccessibleDocument : public SwAccessibleDocumentBase, + public css::accessibility::XAccessibleSelection, + public css::accessibility::XAccessibleExtendedAttributes +{ + // Implementation for XAccessibleSelection interface + SwAccessibleSelectionHelper maSelectionHelper; + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets MULTISELECTABLE(1) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual ~SwAccessibleDocument() override; + +public: + SwAccessibleDocument(std::shared_ptr<SwAccessibleMap> const& pInitMap); + + DECL_LINK( WindowChildEventListener, VclWindowEvent&, void ); + + // XServiceInfo + + // Returns an identifier for the implementation of this object. + virtual OUString SAL_CALL + getImplementationName() override; + + // Return whether the specified service is supported by this class. + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // Returns a list of all supported services. In this case that is just + // the AccessibleContext service. + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XInterface + + // XInterface is inherited through SwAccessibleContext and + // XAccessibleSelection. These methods are needed to avoid + // ambiguities. + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) noexcept override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) noexcept override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleSelection + + virtual void SAL_CALL selectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( + sal_Int32 nChildIndex ) override; + virtual void SAL_CALL clearAccessibleSelection( ) override; + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + // index has to be treated as global child index. + virtual void SAL_CALL deselectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual css::uno::Any SAL_CALL getExtendedAttributes() override; + + // thread safe C++ interface + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + + // XAccessibleComponent + sal_Int32 SAL_CALL getBackground() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accembedded.cxx b/sw/source/core/access/accembedded.cxx new file mode 100644 index 000000000..1eb54cf25 --- /dev/null +++ b/sw/source/core/access/accembedded.cxx @@ -0,0 +1,121 @@ +/* -*- 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 . + */ + +#include <vcl/svapp.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <flyfrm.hxx> +#include "accembedded.hxx" +#include <cntfrm.hxx> +#include <notxtfrm.hxx> +#include <ndole.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::accessibility; + +constexpr OUStringLiteral sImplementationName = u"com.sun.star.comp.Writer.SwAccessibleEmbeddedObject"; + +SwAccessibleEmbeddedObject::SwAccessibleEmbeddedObject( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFlyFrame* pFlyFrame ) : + SwAccessibleNoTextFrame( pInitMap, AccessibleRole::EMBEDDED_OBJECT, pFlyFrame ) +{ +} + +SwAccessibleEmbeddedObject::~SwAccessibleEmbeddedObject() +{ +} + +// XInterface +css::uno::Any SAL_CALL + SwAccessibleEmbeddedObject::queryInterface (const css::uno::Type & rType) +{ + css::uno::Any aReturn = SwAccessibleNoTextFrame::queryInterface (rType); + if ( ! aReturn.hasValue()) + aReturn = ::cppu::queryInterface (rType, + static_cast< css::accessibility::XAccessibleExtendedAttributes* >(this) ); + return aReturn; +} + +void SAL_CALL + SwAccessibleEmbeddedObject::acquire() + noexcept +{ + SwAccessibleNoTextFrame::acquire (); +} + +void SAL_CALL + SwAccessibleEmbeddedObject::release() + noexcept +{ + SwAccessibleNoTextFrame::release (); +} + +OUString SAL_CALL SwAccessibleEmbeddedObject::getImplementationName() +{ + return sImplementationName; +} + +sal_Bool SAL_CALL SwAccessibleEmbeddedObject::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleEmbeddedObject::getSupportedServiceNames() +{ + return { "com.sun.star.text.AccessibleTextEmbeddedObject", sAccessibleServiceName }; +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleEmbeddedObject::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XAccessibleExtendedAttributes +css::uno::Any SAL_CALL SwAccessibleEmbeddedObject::getExtendedAttributes() +{ + SolarMutexGuard g; + + css::uno::Any strRet; + OUString style; + SwFlyFrame* pFFrame = getFlyFrame(); + + if( pFFrame ) + { + style = "style:"; + SwContentFrame* pCFrame; + pCFrame = pFFrame->ContainsContent(); + if( pCFrame ) + { + assert(pCFrame->IsNoTextFrame()); + SwContentNode *const pCNode = static_cast<SwNoTextFrame*>(pCFrame)->GetNode(); + if( pCNode ) + { + style += static_cast<SwOLENode*>(pCNode)->GetOLEObj().GetStyleString(); + } + } + style += ";"; + } + strRet <<= style; + return strRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accembedded.hxx b/sw/source/core/access/accembedded.hxx new file mode 100644 index 000000000..ce82af9e2 --- /dev/null +++ b/sw/source/core/access/accembedded.hxx @@ -0,0 +1,72 @@ +/* -*- 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 . + */ + +#pragma once + +#include "accnotextframe.hxx" + +#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp> + +class SwAccessibleEmbeddedObject : public SwAccessibleNoTextFrame + , public css::accessibility::XAccessibleExtendedAttributes + +{ +protected: + virtual ~SwAccessibleEmbeddedObject() override; + +public: + SwAccessibleEmbeddedObject(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFlyFrame* pFlyFrame ); + + // XInterface + + virtual css::uno::Any SAL_CALL + queryInterface (const css::uno::Type & rType) override; + + virtual void SAL_CALL + acquire() + noexcept override; + + virtual void SAL_CALL + release() + noexcept override; + + // XServiceInfo + + // Returns an identifier for the implementation of this object. + virtual OUString SAL_CALL + getImplementationName() override; + + // Return whether the specified service is supported by this class. + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // Returns a list of all supported services. In this case that is just + // the AccessibleContext service. + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleExtendedAttributes + virtual css::uno::Any SAL_CALL getExtendedAttributes() override ; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfootnote.cxx b/sw/source/core/access/accfootnote.cxx new file mode 100644 index 000000000..6fb7ebca8 --- /dev/null +++ b/sw/source/core/access/accfootnote.cxx @@ -0,0 +1,120 @@ +/* -*- 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 . + */ + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <vcl/svapp.hxx> +#include <ftnfrm.hxx> +#include <fmtftn.hxx> +#include <txtftn.hxx> +#include <viewsh.hxx> +#include <accmap.hxx> +#include "accfootnote.hxx" +#include <strings.hrc> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::accessibility; + +constexpr OUStringLiteral sImplementationNameFootnote + = u"com.sun.star.comp.Writer.SwAccessibleFootnoteView"; +constexpr OUStringLiteral sImplementationNameEndnote + = u"com.sun.star.comp.Writer.SwAccessibleEndnoteView"; + +SwAccessibleFootnote::SwAccessibleFootnote( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + bool bIsEndnote, + const SwFootnoteFrame *pFootnoteFrame ) : + SwAccessibleContext( pInitMap, + bIsEndnote ? AccessibleRole::END_NOTE : AccessibleRole::FOOTNOTE, + pFootnoteFrame ) +{ + TranslateId pResId = bIsEndnote ? STR_ACCESS_ENDNOTE_NAME + : STR_ACCESS_FOOTNOTE_NAME; + + OUString sArg; + const SwTextFootnote *pTextFootnote = + static_cast< const SwFootnoteFrame *>( GetFrame() )->GetAttr(); + if( pTextFootnote ) + { + const SwDoc *pDoc = GetShell()->GetDoc(); + sArg = pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, pFootnoteFrame->getRootFrame()); + } + + SetName(GetResource(pResId, &sArg)); +} + +SwAccessibleFootnote::~SwAccessibleFootnote() +{ +} + +OUString SAL_CALL SwAccessibleFootnote::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + TranslateId pResId = AccessibleRole::END_NOTE == GetRole() + ? STR_ACCESS_ENDNOTE_DESC + : STR_ACCESS_FOOTNOTE_DESC ; + + OUString sArg; + const SwTextFootnote *pTextFootnote = + static_cast< const SwFootnoteFrame *>( GetFrame() )->GetAttr(); + if( pTextFootnote ) + { + const SwDoc *pDoc = GetMap()->GetShell()->GetDoc(); + sArg = pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, GetFrame()->getRootFrame()); + } + + return GetResource(pResId, &sArg); +} + +OUString SAL_CALL SwAccessibleFootnote::getImplementationName() +{ + if( AccessibleRole::END_NOTE == GetRole() ) + return sImplementationNameEndnote; + else + return sImplementationNameFootnote; +} + +sal_Bool SAL_CALL SwAccessibleFootnote::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +Sequence< OUString > SAL_CALL SwAccessibleFootnote::getSupportedServiceNames() +{ + return { (AccessibleRole::END_NOTE == GetRole())?OUString("com.sun.star.text.AccessibleEndnoteView"):OUString("com.sun.star.text.AccessibleFootnoteView"), + sAccessibleServiceName }; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessibleFootnote::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +bool SwAccessibleFootnote::IsEndnote( const SwFootnoteFrame *pFootnoteFrame ) +{ + const SwTextFootnote *pTextFootnote = pFootnoteFrame ->GetAttr(); + return pTextFootnote && pTextFootnote->GetFootnote().IsEndNote() ; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfootnote.hxx b/sw/source/core/access/accfootnote.hxx new file mode 100644 index 000000000..ecc852cd5 --- /dev/null +++ b/sw/source/core/access/accfootnote.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/types.h> +#include "acccontext.hxx" + +class SwAccessibleMap; +class SwFootnoteFrame; + +class SwAccessibleFootnote : public SwAccessibleContext +{ +protected: + virtual ~SwAccessibleFootnote() override; + +public: + SwAccessibleFootnote( std::shared_ptr<SwAccessibleMap> const& pInitMap, + bool bIsEndnote, + const SwFootnoteFrame *pFootnoteFrame ); + + // XAccessibleContext + + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + static bool IsEndnote( const SwFootnoteFrame *pFrame ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accframe.cxx b/sw/source/core/access/accframe.cxx new file mode 100644 index 000000000..684953c6d --- /dev/null +++ b/sw/source/core/access/accframe.cxx @@ -0,0 +1,479 @@ +/* -*- 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 . + */ + +#include <editeng/brushitem.hxx> +#include <flyfrm.hxx> +#include <sectfrm.hxx> +#include <section.hxx> +#include <viewsh.hxx> +#include <viewopt.hxx> +#include <frmatr.hxx> +#include <pagefrm.hxx> +#include <pagedesc.hxx> +#include <fldbas.hxx> +#include <accmap.hxx> +#include "accfrmobjslist.hxx" +#include "accfrmobjmap.hxx" +#include "accframe.hxx" + +using namespace sw::access; + +// Regarding visibility (or in terms of accessibility: regarding the showing +// state): A frame is visible and therefore contained in the tree if its frame +// size overlaps with the visible area. The bounding box however is the +// frame's paint area. +sal_Int32 SwAccessibleFrame::GetChildCount( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame *pFrame, + bool bInPagePreview ) +{ + sal_Int32 nCount = 0; + + const SwAccessibleChildSList aVisList( rVisArea, *pFrame, rAccMap ); + + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( bInPagePreview ) ) + { + nCount++; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + nCount += GetChildCount( rAccMap, + rVisArea, rLower.GetSwFrame(), + bInPagePreview ); + } + ++aIter; + } + + return nCount; +} + +SwAccessibleChild SwAccessibleFrame::GetChild( + SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + sal_Int32& rPos, + bool bInPagePreview ) +{ + SwAccessibleChild aRet; + + if( rPos >= 0 ) + { + if( SwAccessibleChildMap::IsSortingRequired( rFrame ) ) + { + // We need a sorted list here + const SwAccessibleChildMap aVisMap( rVisArea, rFrame, rAccMap ); + SwAccessibleChildMap::const_iterator aIter( aVisMap.cbegin() ); + while( aIter != aVisMap.cend() && !aRet.IsValid() ) + { + const SwAccessibleChild& rLower = (*aIter).second; + if( rLower.IsAccessible( bInPagePreview ) ) + { + if( 0 == rPos ) + aRet = rLower; + else + rPos--; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + aRet = GetChild( rAccMap, + rVisArea, *(rLower.GetSwFrame()), rPos, + bInPagePreview ); + } + ++aIter; + } + } + else + { + // The unsorted list is sorted enough, because it returns lower + // frames in the correct order. + const SwAccessibleChildSList aVisList( rVisArea, rFrame, rAccMap ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() && !aRet.IsValid() ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( bInPagePreview ) ) + { + if( 0 == rPos ) + aRet = rLower; + else + rPos--; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + aRet = GetChild( rAccMap, + rVisArea, *(rLower.GetSwFrame()), rPos, + bInPagePreview ); + } + ++aIter; + } + } + } + + return aRet; +} + +bool SwAccessibleFrame::GetChildIndex( + SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + const SwAccessibleChild& rChild, + sal_Int32& rPos, + bool bInPagePreview ) +{ + bool bFound = false; + + if( SwAccessibleChildMap::IsSortingRequired( rFrame ) ) + { + // We need a sorted list here + const SwAccessibleChildMap aVisMap( rVisArea, rFrame, rAccMap ); + SwAccessibleChildMap::const_iterator aIter( aVisMap.cbegin() ); + while( aIter != aVisMap.cend() && !bFound ) + { + const SwAccessibleChild& rLower = (*aIter).second; + if( rLower.IsAccessible( bInPagePreview ) ) + { + if( rChild == rLower ) + bFound = true; + else + rPos++; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + bFound = GetChildIndex( rAccMap, + rVisArea, *(rLower.GetSwFrame()), rChild, + rPos, bInPagePreview ); + } + ++aIter; + } + } + else + { + // The unsorted list is sorted enough, because it returns lower + // frames in the correct order. + + const SwAccessibleChildSList aVisList( rVisArea, rFrame, rAccMap ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() && !bFound ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( bInPagePreview ) ) + { + if( rChild == rLower ) + bFound = true; + else + rPos++; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + bFound = GetChildIndex( rAccMap, + rVisArea, *(rLower.GetSwFrame()), rChild, + rPos, bInPagePreview ); + } + ++aIter; + } + } + + return bFound; +} + +SwAccessibleChild SwAccessibleFrame::GetChildAtPixel( const SwRect& rVisArea, + const SwFrame& rFrame, + const Point& rPixPos, + bool bInPagePreview, + SwAccessibleMap& rAccMap ) +{ + SwAccessibleChild aRet; + + if( SwAccessibleChildMap::IsSortingRequired( rFrame ) ) + { + // We need a sorted list here, and we have to reverse iterate, + // because objects in front should be returned. + const SwAccessibleChildMap aVisMap( rVisArea, rFrame, rAccMap ); + SwAccessibleChildMap::const_reverse_iterator aRIter( aVisMap.crbegin() ); + while( aRIter != aVisMap.crend() && !aRet.IsValid() ) + { + const SwAccessibleChild& rLower = (*aRIter).second; + // A frame is returned if it's frame size is inside the visarea + // and the position is inside the frame's paint area. + if( rLower.IsAccessible( bInPagePreview ) ) + { + SwRect aLogBounds( rLower.GetBounds( rAccMap ) ); + if( !aLogBounds.IsEmpty() ) + { + tools::Rectangle aPixBounds( rAccMap.CoreToPixel( aLogBounds ) ); + if( aPixBounds.Contains( rPixPos ) ) + aRet = rLower; + } + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + aRet = GetChildAtPixel( rVisArea, *(rLower.GetSwFrame()), rPixPos, + bInPagePreview, rAccMap ); + } + ++aRIter; + } + } + else + { + // The unsorted list is sorted enough, because it returns lower + // frames in the correct order. Moreover, we can iterate forward, + // because the lowers don't overlap! + const SwAccessibleChildSList aVisList( rVisArea, rFrame, rAccMap ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() && !aRet.IsValid() ) + { + const SwAccessibleChild& rLower = *aIter; + // A frame is returned if it's frame size is inside the visarea + // and the position is inside the frame's paint area. + if( rLower.IsAccessible( bInPagePreview ) ) + { + SwRect aLogBounds( rLower.GetBounds( rAccMap ) ); + if( !aLogBounds.IsEmpty() ) + { + tools::Rectangle aPixBounds( rAccMap.CoreToPixel( aLogBounds ) ); + if( aPixBounds.Contains( rPixPos ) ) + aRet = rLower; + } + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + aRet = GetChildAtPixel( rVisArea, *(rLower.GetSwFrame()), rPixPos, + bInPagePreview, rAccMap ); + } + ++aIter; + } + } + + return aRet; +} + +void SwAccessibleFrame::GetChildren( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + std::list< SwAccessibleChild >& rChildren, + bool bInPagePreview ) +{ + if( SwAccessibleChildMap::IsSortingRequired( rFrame ) ) + { + // We need a sorted list here + const SwAccessibleChildMap aVisMap( rVisArea, rFrame, rAccMap ); + SwAccessibleChildMap::const_iterator aIter( aVisMap.cbegin() ); + while( aIter != aVisMap.cend() ) + { + const SwAccessibleChild& rLower = (*aIter).second; + if( rLower.IsAccessible( bInPagePreview ) ) + { + rChildren.push_back( rLower ); + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + GetChildren( rAccMap, rVisArea, *(rLower.GetSwFrame()), + rChildren, bInPagePreview ); + } + ++aIter; + } + } + else + { + // The unsorted list is sorted enough, because it returns lower + // frames in the correct order. + const SwAccessibleChildSList aVisList( rVisArea, rFrame, rAccMap ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( bInPagePreview ) ) + { + rChildren.push_back( rLower ); + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + GetChildren( rAccMap, rVisArea, *(rLower.GetSwFrame()), + rChildren, bInPagePreview ); + } + ++aIter; + } + } +} + +SwRect SwAccessibleFrame::GetBounds( const SwAccessibleMap& rAccMap, + const SwFrame *pFrame ) +{ + if( !pFrame ) + pFrame = GetFrame(); + + SwAccessibleChild aFrame( pFrame ); + SwRect aBounds( aFrame.GetBounds( rAccMap ).Intersection( maVisArea ) ); + return aBounds; +} + +bool SwAccessibleFrame::IsEditable( SwViewShell const *pVSh ) const +{ + const SwFrame *pFrame = GetFrame(); + if( !pFrame ) + return false; + + OSL_ENSURE( pVSh, "no view shell" ); + if( pVSh && (pVSh->GetViewOptions()->IsReadonly() || + pVSh->IsPreview()) ) + return false; + + if( !pFrame->IsRootFrame() && pFrame->IsProtected() ) + return false; + + return true; +} + +bool SwAccessibleFrame::IsOpaque( SwViewShell const *pVSh ) const +{ + SwAccessibleChild aFrame( GetFrame() ); + if( !aFrame.GetSwFrame() ) + return false; + + OSL_ENSURE( pVSh, "no view shell" ); + if( !pVSh ) + return false; + + const SwViewOption *pVOpt = pVSh->GetViewOptions(); + do + { + const SwFrame *pFrame = aFrame.GetSwFrame(); + if( pFrame->IsRootFrame() ) + return true; + + if( pFrame->IsPageFrame() && !pVOpt->IsPageBack() ) + return false; + + const SvxBrushItem &rBack = pFrame->GetAttrSet()->GetBackground(); + if( !rBack.GetColor().IsTransparent() || + rBack.GetGraphicPos() != GPOS_NONE ) + return true; + + // If a fly frame has a transparent background color, we have to consider the background. + // But a background color "no fill"/"auto fill" should *not* be considered. + if( pFrame->IsFlyFrame() && + rBack.GetColor().IsTransparent() && + rBack.GetColor() != COL_TRANSPARENT + ) + return true; + + if( pFrame->IsSctFrame() ) + { + const SwSection* pSection = static_cast<const SwSectionFrame*>(pFrame)->GetSection(); + if( pSection && ( SectionType::ToxHeader == pSection->GetType() || + SectionType::ToxContent == pSection->GetType() ) && + !pVOpt->IsReadonly() && + SwViewOption::IsIndexShadings() ) + return true; + } + if( pFrame->IsFlyFrame() ) + aFrame = static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(); + else + aFrame = pFrame->GetUpper(); + } while( aFrame.GetSwFrame() && !aFrame.IsAccessible( IsInPagePreview() ) ); + + return false; +} + +SwAccessibleFrame::SwAccessibleFrame( const SwRect& rVisArea, + const SwFrame *pF, + bool bIsPagePreview ) : + maVisArea( rVisArea ), + mpFrame( pF ), + mbIsInPagePreview( bIsPagePreview ) +{ + assert(mpFrame); +} + +SwAccessibleFrame::~SwAccessibleFrame() +{ +} + +const SwFrame* SwAccessibleFrame::GetParent( const SwAccessibleChild& rFrameOrObj, + bool bInPagePreview ) +{ + return rFrameOrObj.GetParent( bInPagePreview ); +} + +OUString SwAccessibleFrame::GetFormattedPageNumber() const +{ + sal_uInt16 nPageNum = GetFrame()->GetVirtPageNum(); + SvxNumType nFormat = GetFrame()->FindPageFrame()->GetPageDesc() + ->GetNumType().GetNumberingType(); + if( SVX_NUM_NUMBER_NONE == nFormat ) + nFormat = SVX_NUM_ARABIC; + + OUString sRet( FormatNumber( nPageNum, nFormat ) ); + return sRet; +} + +sal_Int32 SwAccessibleFrame::GetChildCount( SwAccessibleMap& rAccMap ) const +{ + return GetChildCount( rAccMap, maVisArea, mpFrame, IsInPagePreview() ); +} + +sw::access::SwAccessibleChild SwAccessibleFrame::GetChild( + SwAccessibleMap& rAccMap, + sal_Int32 nPos ) const +{ + return SwAccessibleFrame::GetChild( rAccMap, maVisArea, *mpFrame, nPos, IsInPagePreview() ); +} + +sal_Int32 SwAccessibleFrame::GetChildIndex( SwAccessibleMap& rAccMap, + const sw::access::SwAccessibleChild& rChild ) const +{ + sal_Int32 nPos = 0; + return GetChildIndex( rAccMap, maVisArea, *mpFrame, rChild, nPos, IsInPagePreview() ) + ? nPos + : -1; +} + +sw::access::SwAccessibleChild SwAccessibleFrame::GetChildAtPixel( + const Point& rPos, + SwAccessibleMap& rAccMap ) const +{ + return GetChildAtPixel( maVisArea, *mpFrame, rPos, IsInPagePreview(), rAccMap ); +} + +void SwAccessibleFrame::GetChildren( SwAccessibleMap& rAccMap, + std::list< sw::access::SwAccessibleChild >& rChildren ) const +{ + GetChildren( rAccMap, maVisArea, *mpFrame, rChildren, IsInPagePreview() ); +} + +bool SwAccessibleFrame::IsShowing( const SwAccessibleMap& rAccMap, + const sw::access::SwAccessibleChild& rFrameOrObj ) const +{ + return IsShowing( rFrameOrObj.GetBox( rAccMap ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accframe.hxx b/sw/source/core/access/accframe.hxx new file mode 100644 index 000000000..6f2dff0ae --- /dev/null +++ b/sw/source/core/access/accframe.hxx @@ -0,0 +1,163 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRAME_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRAME_HXX + +#include <swrect.hxx> + +#include <sal/types.h> +#include <rtl/ustring.hxx> + +#include <list> +#include "accfrmobj.hxx" + +class SwAccessibleMap; +class SwFrame; +class SwViewShell; + +// Any method of this class must be called with an acquired solar mutex! + +class SwAccessibleFrame +{ + SwRect maVisArea; + const SwFrame* mpFrame; + const bool mbIsInPagePreview; + +protected: + // #i77106# - method needs to be called by new class <SwAccessibleTableColHeaders> + static sal_Int32 GetChildCount( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame *pFrame, + bool bInPagePreviewr ); + +// private: + static sw::access::SwAccessibleChild GetChild( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + sal_Int32& rPos, + bool bInPagePreview); + + static bool GetChildIndex( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + const sw::access::SwAccessibleChild& rChild, + sal_Int32& rPos, + bool bInPagePreview ); + + static sw::access::SwAccessibleChild GetChildAtPixel( const SwRect& rVisArea, + const SwFrame& rFrame, + const Point& rPos, + bool bInPagePreview, + SwAccessibleMap& rAccMap ); + + static void GetChildren( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + std::list< sw::access::SwAccessibleChild >& rChildren, + bool bInPagePreview ); + + bool IsEditable( SwViewShell const *pVSh ) const; + + bool IsOpaque( SwViewShell const *pVSh ) const; + +public: + bool IsShowing( const SwAccessibleMap& rAccMap, + const sw::access::SwAccessibleChild& rFrameOrObj ) const; + inline bool IsShowing( const SwRect& rFrame ) const; + inline bool IsShowing( const SwAccessibleMap& rAccMap ) const; + +protected: + bool IsInPagePreview() const + { + return mbIsInPagePreview; + } + + void ClearFrame() + { + mpFrame = nullptr; + } + + SwAccessibleFrame( const SwRect& rVisArea, + const SwFrame *pFrame, + bool bIsPagePreview ); + virtual ~SwAccessibleFrame(); +public: + // Return the SwFrame this context is attached to. + const SwFrame* GetFrame() const { return mpFrame; }; + + static const SwFrame* GetParent( const sw::access::SwAccessibleChild& rFrameOrObj, + bool bInPagePreview ); + + sal_Int32 GetChildIndex( SwAccessibleMap& rAccMap, + const sw::access::SwAccessibleChild& rChild ) const; + +protected: + // Return the bounding box of the frame clipped to the visible area. + // If no frame is specified, use this' frame. + SwRect GetBounds( const SwAccessibleMap& rAccMap, + const SwFrame *pFrame = nullptr ); + + // Return the upper that has a context attached. This might be + // another one than the immediate upper. + inline const SwFrame *GetParent() const; + + // Return the lower count or the nth lower, there the lowers have a + // not be same one as the SwFrame's lowers + sal_Int32 GetChildCount( SwAccessibleMap& rAccMap ) const; + sw::access::SwAccessibleChild GetChild( SwAccessibleMap& rAccMap, + sal_Int32 nPos ) const; + sw::access::SwAccessibleChild GetChildAtPixel( const Point& rPos, + SwAccessibleMap& rAccMap ) const; + void GetChildren( SwAccessibleMap& rAccMap, + std::list< sw::access::SwAccessibleChild >& rChildren ) const; + + void SetVisArea( const SwRect& rNewVisArea ) + { + maVisArea = rNewVisArea; + } + + const SwRect& GetVisArea() const + { + return maVisArea; + } + + OUString GetFormattedPageNumber() const; +}; + +inline bool SwAccessibleFrame::IsShowing( const SwRect& rFrame ) const +{ + return !rFrame.IsEmpty() && rFrame.Overlaps( maVisArea ); +} + +inline bool SwAccessibleFrame::IsShowing( const SwAccessibleMap& rAccMap ) const +{ + sw::access::SwAccessibleChild aFrameOrObj( GetFrame() ); + return IsShowing( rAccMap, aFrameOrObj ); +} + +inline const SwFrame *SwAccessibleFrame::GetParent() const +{ + sw::access::SwAccessibleChild aFrameOrObj( GetFrame() ); + return GetParent( aFrameOrObj, IsInPagePreview() ); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accframebase.cxx b/sw/source/core/access/accframebase.cxx new file mode 100644 index 000000000..c9a1ea73e --- /dev/null +++ b/sw/source/core/access/accframebase.cxx @@ -0,0 +1,373 @@ +/* -*- 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 . + */ + +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <unotools/accessiblestatesethelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <frmfmt.hxx> +#include <flyfrm.hxx> +#include <fmtcntnt.hxx> +#include <ndindex.hxx> +#include <fesh.hxx> +#include <hints.hxx> +#include <accmap.hxx> +#include "accframebase.hxx" + +#include <crsrsh.hxx> +#include <notxtfrm.hxx> +#include <ndtxt.hxx> +#include <undobj.hxx> +#include <fmtanchr.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +bool SwAccessibleFrameBase::IsSelected() +{ + bool bRet = false; + + assert(GetMap()); + const SwViewShell *pVSh = GetMap()->GetShell(); + assert(pVSh); + if( auto pFESh = dynamic_cast<const SwFEShell*>(pVSh) ) + { + const SwFrame *pFlyFrame = pFESh->GetSelectedFlyFrame(); + if( pFlyFrame == GetFrame() ) + bRet = true; + } + + return bRet; +} + +void SwAccessibleFrameBase::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + const SwViewShell *pVSh = GetMap()->GetShell(); + assert(pVSh); + + if (dynamic_cast<const SwFEShell*>(pVSh)) + { + // SELECTABLE + rStateSet.AddState(AccessibleStateType::SELECTABLE); + // FOCUSABLE + rStateSet.AddState(AccessibleStateType::FOCUSABLE); + } + + // SELECTED and FOCUSED + if( IsSelected() ) + { + rStateSet.AddState( AccessibleStateType::SELECTED ); + SAL_WARN_IF(!m_bIsSelected, "sw.a11y", "bSelected out of sync"); + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + } + if( GetSelectedState() ) + rStateSet.AddState( AccessibleStateType::SELECTED ); +} + +SwNodeType SwAccessibleFrameBase::GetNodeType( const SwFlyFrame *pFlyFrame ) +{ + SwNodeType nType = SwNodeType::Text; + if( pFlyFrame->Lower() ) + { + if( pFlyFrame->Lower()->IsNoTextFrame() ) + { + const SwNoTextFrame *const pContentFrame = + static_cast<const SwNoTextFrame *>(pFlyFrame->Lower()); + nType = pContentFrame->GetNode()->GetNodeType(); + } + } + else + { + const SwFrameFormat *pFrameFormat = pFlyFrame->GetFormat(); + const SwFormatContent& rContent = pFrameFormat->GetContent(); + const SwNodeIndex *pNdIdx = rContent.GetContentIdx(); + if( pNdIdx ) + { + const SwContentNode *pCNd = + (pNdIdx->GetNodes())[pNdIdx->GetIndex()+1]->GetContentNode(); + if( pCNd ) + nType = pCNd->GetNodeType(); + } + } + + return nType; +} + +SwAccessibleFrameBase::SwAccessibleFrameBase( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + sal_Int16 nInitRole, + const SwFlyFrame* pFlyFrame ) : + SwAccessibleContext( pInitMap, nInitRole, pFlyFrame ), + m_bIsSelected( false ) +{ + const SwFrameFormat* pFrameFormat = pFlyFrame->GetFormat(); + if(pFrameFormat) + StartListening(const_cast<SwFrameFormat*>(pFrameFormat)->GetNotifier()); + + SetName( pFrameFormat->GetName() ); + + m_bIsSelected = IsSelected(); +} + +void SwAccessibleFrameBase::InvalidateCursorPos_() +{ + bool bNewSelected = IsSelected(); + bool bOldSelected; + + { + std::scoped_lock aGuard( m_Mutex ); + bOldSelected = m_bIsSelected; + m_bIsSelected = bNewSelected; + } + + if( bNewSelected ) + { + // remember that object as the one that has the caret. This is + // necessary to notify that object if the cursor leaves it. + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + if( bOldSelected == bNewSelected ) + return; + + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() && bNewSelected ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, bNewSelected ); + if( pWin && pWin->HasFocus() && !bNewSelected ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, bNewSelected ); + if(!bNewSelected) + return; + + uno::Reference< XAccessible > xParent( GetWeakParent() ); + if( xParent.is() ) + { + SwAccessibleContext *pAcc = + static_cast <SwAccessibleContext *>( xParent.get() ); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + uno::Reference< XAccessible > xChild(this); + aEvent.NewValue <<= xChild; + pAcc->FireAccessibleEvent( aEvent ); + } +} + +void SwAccessibleFrameBase::InvalidateFocus_() +{ + vcl::Window *pWin = GetWindow(); + if( !pWin ) + return; + + bool bSelected; + + { + std::scoped_lock aGuard( m_Mutex ); + bSelected = m_bIsSelected; + } + assert(bSelected && "focus object should be selected"); + + FireStateChangedEvent( AccessibleStateType::FOCUSED, + pWin->HasFocus() && bSelected ); +} + +bool SwAccessibleFrameBase::HasCursor() +{ + std::scoped_lock aGuard( m_Mutex ); + return m_bIsSelected; +} + +SwAccessibleFrameBase::~SwAccessibleFrameBase() +{ +} + +void SwAccessibleFrameBase::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + EndListeningAll(); + } + else if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacyModifyHint = static_cast<const sw::LegacyModifyHint*>(&rHint); + const sal_uInt16 nWhich = pLegacyModifyHint->GetWhich(); + const SwFlyFrame* pFlyFrame = static_cast<const SwFlyFrame*>(GetFrame()); + if(nWhich == RES_NAME_CHANGED && pFlyFrame) + { + const SwFrameFormat* pFrameFormat = pFlyFrame->GetFormat(); + + const OUString sOldName( GetName() ); + assert( !pLegacyModifyHint->m_pOld || + static_cast<const SwStringMsgPoolItem *>(pLegacyModifyHint->m_pOld)->GetString() == GetName()); + + SetName( pFrameFormat->GetName() ); + assert( !pLegacyModifyHint->m_pNew || + static_cast<const SwStringMsgPoolItem *>(pLegacyModifyHint->m_pNew)->GetString() == GetName()); + + if( sOldName != GetName() ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + aEvent.OldValue <<= sOldName; + aEvent.NewValue <<= GetName(); + FireAccessibleEvent( aEvent ); + } + } + } +} + +void SwAccessibleFrameBase::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + SolarMutexGuard aGuard; + EndListeningAll(); + SwAccessibleContext::Dispose(bRecursive, bCanSkipInvisible); +} + +//Get the selection cursor of the document. +SwPaM* SwAccessibleFrameBase::GetCursor() +{ + // get the cursor shell; if we don't have any, we don't have a + // cursor/selection either + SwPaM* pCursor = nullptr; + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr && !pCursorShell->IsTableMode() ) + { + SwFEShell *pFESh = dynamic_cast<SwFEShell*>( pCursorShell); + if( !pFESh || + !(pFESh->IsFrameSelected() || pFESh->IsObjSelected() > 0) ) + { + // get the selection, and test whether it affects our text node + pCursor = pCursorShell->GetCursor( false /* ??? */ ); + } + } + + return pCursor; +} + +//Return the selected state of the object. +//when the object's anchor are in the selection cursor, we should return true. +bool SwAccessibleFrameBase::GetSelectedState( ) +{ + SolarMutexGuard aGuard; + + if(GetMap()->IsDocumentSelAll()) + { + return true; + } + + // SELECTED. + SwFlyFrame* pFlyFrame = getFlyFrame(); + const SwFrameFormat *pFrameFormat = pFlyFrame->GetFormat(); + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + const SwPosition *pPos = rAnchor.GetContentAnchor(); + if( !pPos ) + return false; + int nIndex = pPos->nContent.GetIndex(); + if( pPos->nNode.GetNode().GetTextNode() ) + { + SwPaM* pCursor = GetCursor(); + if( pCursor != nullptr ) + { + const SwTextNode* pNode = pPos->nNode.GetNode().GetTextNode(); + SwNodeOffset nHere = pNode->GetIndex(); + + // iterate over ring + SwPaM* pRingStart = pCursor; + do + { + // ignore, if no mark + if( pCursor->HasMark() ) + { + // check whether nHere is 'inside' pCursor + SwPosition* pStart = pCursor->Start(); + SwNodeOffset nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = pCursor->End(); + SwNodeOffset nEndIndex = pEnd->nNode.GetIndex(); + if( ( nHere >= nStartIndex ) && (nHere <= nEndIndex) ) + { + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + if( ((nHere == nStartIndex) && (nIndex >= pStart->nContent.GetIndex())) || (nHere > nStartIndex) ) + if( ((nHere == nEndIndex) && (nIndex < pEnd->nContent.GetIndex())) || (nHere < nEndIndex) ) + return true; + } + else if( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA ) + { + if (IsSelectFrameAnchoredAtPara(*pPos, *pStart, *pEnd)) + return true; + } + else if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + if (IsDestroyFrameAnchoredAtChar(*pPos, *pStart, *pEnd)) + { + return true; + } + } + break; + } + // else: this PaM doesn't point to this paragraph + } + // else: this PaM is collapsed and doesn't select anything + + // next PaM in ring + pCursor = pCursor->GetNext(); + } + while( pCursor != pRingStart ); + } + } + return false; +} + +SwFlyFrame* SwAccessibleFrameBase::getFlyFrame() const +{ + SwFlyFrame* pFlyFrame = nullptr; + + const SwFrame* pFrame = GetFrame(); + assert(pFrame); + if( pFrame->IsFlyFrame() ) + { + pFlyFrame = static_cast<SwFlyFrame*>( const_cast<SwFrame*>( pFrame ) ); + } + + return pFlyFrame; +} + +bool SwAccessibleFrameBase::SetSelectedState( bool ) +{ + bool bParaSelected = GetSelectedState() || IsSelected(); + + if (m_isSelectedInDoc != bParaSelected) + { + m_isSelectedInDoc = bParaSelected; + FireStateChangedEvent( AccessibleStateType::SELECTED, bParaSelected ); + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accframebase.hxx b/sw/source/core/access/accframebase.hxx new file mode 100644 index 000000000..12af1f52d --- /dev/null +++ b/sw/source/core/access/accframebase.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRAMEBASE_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRAMEBASE_HXX + +#include "acccontext.hxx" +#include <svl/listener.hxx> +#include <ndtyp.hxx> + +class SwFlyFrame; + +class SwAccessibleFrameBase : public SwAccessibleContext, public SvtListener +{ + bool m_bIsSelected; // protected by base class mutex + bool IsSelected(); + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets SELECTABLE(1), SELECTED(+), + // FOCUSABLE(1) and FOCUSED(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + SwFlyFrame* getFlyFrame() const; + bool GetSelectedState( ); + SwPaM* GetCursor(); + + virtual void InvalidateCursorPos_() override; + virtual void InvalidateFocus_() override; + + virtual ~SwAccessibleFrameBase() override; + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleFrameBase(std::shared_ptr<SwAccessibleMap> const& pInitMap, + sal_Int16 nInitRole, + const SwFlyFrame *pFlyFrame ); + + virtual bool HasCursor() override; // required by map to remember that object + + static SwNodeType GetNodeType( const SwFlyFrame *pFlyFrame ); + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + virtual bool SetSelectedState( bool bSelected ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobj.cxx b/sw/source/core/access/accfrmobj.cxx new file mode 100644 index 000000000..c09c0215e --- /dev/null +++ b/sw/source/core/access/accfrmobj.cxx @@ -0,0 +1,408 @@ +/* -*- 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 . + */ + +#include "accfrmobj.hxx" + +#include <accmap.hxx> +#include "acccontext.hxx" + +#include <viewsh.hxx> +#include <rootfrm.hxx> +#include <flyfrm.hxx> +#include <pagefrm.hxx> +#include <cellfrm.hxx> +#include <swtable.hxx> +#include <dflyobj.hxx> +#include <frmfmt.hxx> +#include <fmtanchr.hxx> +#include <dcontact.hxx> + +#include <vcl/window.hxx> + +namespace sw::access { + +SwAccessibleChild::SwAccessibleChild() + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{} + +SwAccessibleChild::SwAccessibleChild( const SdrObject* pDrawObj ) + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{ + Init( pDrawObj ); +} + +SwAccessibleChild::SwAccessibleChild( const SwFrame* pFrame ) + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{ + Init( pFrame ); +} + +SwAccessibleChild::SwAccessibleChild( vcl::Window* pWindow ) + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{ + Init( pWindow ); +} + +SwAccessibleChild::SwAccessibleChild( const SwFrame* pFrame, + const SdrObject* pDrawObj, + vcl::Window* pWindow ) + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{ + if ( pFrame ) + { + Init( pFrame ); + } + else if ( pDrawObj ) + { + Init( pDrawObj ); + } + else if ( pWindow ) + { + Init( pWindow ); + } + OSL_ENSURE( (!pFrame || pFrame == mpFrame) && + (!pDrawObj || pDrawObj == mpDrawObj) && + (!pWindow || pWindow == mpWindow), + "invalid frame/object/window combination" ); + +} + +SwAccessibleChild::~SwAccessibleChild() = default; + +void SwAccessibleChild::Init( const SdrObject* pDrawObj ) +{ + mpDrawObj = pDrawObj; + const SwVirtFlyDrawObj* pFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>(mpDrawObj); + mpFrame = pFlyDrawObj ? pFlyDrawObj->GetFlyFrame() : nullptr; + mpWindow = nullptr; +} + +void SwAccessibleChild::Init( const SwFrame* pFrame ) +{ + mpFrame = pFrame; + mpDrawObj = mpFrame && mpFrame->IsFlyFrame() + ? static_cast < const SwFlyFrame * >( mpFrame )->GetVirtDrawObj() + : nullptr; + mpWindow = nullptr; +} + +void SwAccessibleChild::Init( vcl::Window* pWindow ) +{ + mpWindow = pWindow; + mpFrame = nullptr; + mpDrawObj = nullptr; +} + +bool SwAccessibleChild::IsAccessible( bool bPagePreview ) const +{ + bool bRet( false ); + + if ( mpFrame ) + { + bRet = mpFrame->IsAccessibleFrame() && + ( !mpFrame->IsCellFrame() || + static_cast<const SwCellFrame *>( mpFrame )->GetTabBox()->GetSttNd() != nullptr ) && + !mpFrame->IsInCoveredCell() && + ( bPagePreview || + !mpFrame->IsPageFrame() ); + } + else if ( mpDrawObj ) + { + bRet = true; + } + else if ( mpWindow ) + { + bRet = true; + } + + return bRet; +} + +bool SwAccessibleChild::IsBoundAsChar() const +{ + bool bRet( false ); + + if ( mpFrame ) + { + bRet = mpFrame->IsFlyFrame() && + static_cast< const SwFlyFrame *>(mpFrame)->IsFlyInContentFrame(); + } + else if ( mpDrawObj ) + { + const SwFrameFormat* pFrameFormat = ::FindFrameFormat( mpDrawObj ); + bRet = pFrameFormat + && (RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId()); + } + else if ( mpWindow ) + { + bRet = false; + } + + return bRet; +} + +SwAccessibleChild& SwAccessibleChild::operator=( const SdrObject* pDrawObj ) +{ + Init( pDrawObj ); + return *this; +} + +SwAccessibleChild& SwAccessibleChild::operator=( const SwFrame* pFrame ) +{ + Init( pFrame ); + return *this; +} + +SwAccessibleChild& SwAccessibleChild::operator=( vcl::Window* pWindow ) +{ + Init( pWindow ); + return *this; +} + +bool SwAccessibleChild::operator==( const SwAccessibleChild& r ) const +{ + return mpFrame == r.mpFrame && + mpDrawObj == r.mpDrawObj && + mpWindow == r.mpWindow; +} + +bool SwAccessibleChild::IsValid() const +{ + return mpFrame != nullptr || + mpDrawObj != nullptr || + mpWindow != nullptr; +} + +bool SwAccessibleChild::IsVisibleChildrenOnly() const +{ + bool bRet( false ); + + if ( !mpFrame ) + { + bRet = true; + } + else + { + bRet = mpFrame->IsRootFrame() || + !( mpFrame->IsTabFrame() || + mpFrame->IsInTab() || + ( IsBoundAsChar() && + static_cast<const SwFlyFrame*>(mpFrame)->GetAnchorFrame()->IsInTab() ) ); + } + + return bRet; +} + +SwRect SwAccessibleChild::GetBox( const SwAccessibleMap& rAccMap ) const +{ + SwRect aBox; + + if ( mpFrame ) + { + if ( mpFrame->IsPageFrame() && + static_cast< const SwPageFrame * >( mpFrame )->IsEmptyPage() ) + { + aBox = SwRect( mpFrame->getFrameArea().Left(), mpFrame->getFrameArea().Top()-1, 1, 1 ); + } + else if ( mpFrame->IsTabFrame() ) + { + aBox = mpFrame->getFrameArea(); + aBox.Intersection( mpFrame->GetUpper()->getFrameArea() ); + } + else + { + aBox = mpFrame->getFrameArea(); + } + } + else if( mpDrawObj ) + { + const SwContact* const pContact = ::GetUserCall(mpDrawObj); + // assume that a) the SwVirt* objects that don't have this are handled + // by the mpFrame case above b) for genuine SdrObject this must be set + // if it's connected to layout + assert(dynamic_cast<SwDrawContact const*>(pContact)); + SwPageFrame const*const pPage(const_cast<SwAnchoredObject *>( + pContact->GetAnchoredObj(mpDrawObj))->FindPageFrameOfAnchor()); + if (pPage) // may end up here with partial layout -> not visible + { + aBox = SwRect( mpDrawObj->GetCurrentBoundRect() ); + // tdf#91260 drawing object may be partially off-page + aBox.Intersection(pPage->getFrameArea()); + } + } + else if ( mpWindow ) + { + vcl::Window *pWin = rAccMap.GetShell()->GetWin(); + if (pWin) + { + aBox = SwRect( pWin->PixelToLogic( + tools::Rectangle( mpWindow->GetPosPixel(), + mpWindow->GetSizePixel() ) ) ); + } + } + + return aBox; +} + +SwRect SwAccessibleChild::GetBounds( const SwAccessibleMap& rAccMap ) const +{ + SwRect aBound; + + if( mpFrame ) + { + if( mpFrame->IsPageFrame() && + static_cast< const SwPageFrame * >( mpFrame )->IsEmptyPage() ) + { + aBound = SwRect( mpFrame->getFrameArea().Left(), mpFrame->getFrameArea().Top()-1, 0, 0 ); + } + else + aBound = mpFrame->GetPaintArea(); + } + else if( mpDrawObj ) + { + aBound = GetBox( rAccMap ); + } + else if ( mpWindow ) + { + aBound = GetBox( rAccMap ); + } + + return aBound; +} + +bool SwAccessibleChild::AlwaysIncludeAsChild() const +{ + bool bAlwaysIncludedAsChild( false ); + + if ( mpWindow ) + { + bAlwaysIncludedAsChild = true; + } + + return bAlwaysIncludedAsChild; +} + +const SwFrame* SwAccessibleChild::GetParent( const bool bInPagePreview ) const +{ + const SwFrame* pParent( nullptr ); + + if ( mpFrame ) + { + if( mpFrame->IsFlyFrame() ) + { + const SwFlyFrame* pFly = static_cast< const SwFlyFrame *>( mpFrame ); + if( pFly->IsFlyInContentFrame() ) + { + // For RndStdIds::FLY_AS_CHAR the parent is the anchor + pParent = pFly->GetAnchorFrame(); + OSL_ENSURE( SwAccessibleChild( pParent ).IsAccessible( bInPagePreview ), + "parent is not accessible" ); + } + else + { + // In any other case the parent is the root frm + // (in page preview, the page frame) + if( bInPagePreview ) + pParent = pFly->FindPageFrame(); + else + pParent = pFly->getRootFrame(); + } + } + else + { + SwAccessibleChild aUpper( mpFrame->GetUpper() ); + while( aUpper.GetSwFrame() && !aUpper.IsAccessible(bInPagePreview) ) + { + aUpper = aUpper.GetSwFrame()->GetUpper(); + } + pParent = aUpper.GetSwFrame(); + } + } + else if( mpDrawObj ) + { + const SwDrawContact *pContact = + static_cast< const SwDrawContact* >( GetUserCall( mpDrawObj ) ); + OSL_ENSURE( pContact, "sdr contact is missing" ); + if( pContact ) + { + const SwFrameFormat *pFrameFormat = pContact->GetFormat(); + OSL_ENSURE( pFrameFormat, "frame format is missing" ); + if( pFrameFormat && RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId() ) + { + // For RndStdIds::FLY_AS_CHAR the parent is the anchor + pParent = pContact->GetAnchorFrame(); + OSL_ENSURE( SwAccessibleChild( pParent ).IsAccessible( bInPagePreview ), + "parent is not accessible" ); + + } + else + { + // In any other case the parent is the root frm + SwFrame const*const pAnchor(pContact->GetAnchorFrame()); + if (pAnchor) // null if object removed from layout + { + if (bInPagePreview) + pParent = pAnchor->FindPageFrame(); + else + pParent = pAnchor->getRootFrame(); + } + } + } + } + else if ( mpWindow ) + { + css::uno::Reference < css::accessibility::XAccessible > xAcc = + mpWindow->GetAccessible(); + if ( xAcc.is() ) + { + css::uno::Reference < css::accessibility::XAccessibleContext > xAccContext = + xAcc->getAccessibleContext(); + if ( xAccContext.is() ) + { + css::uno::Reference < css::accessibility::XAccessible > xAccParent = + xAccContext->getAccessibleParent(); + if ( xAccParent.is() ) + { + SwAccessibleContext* pAccParentImpl = + dynamic_cast< SwAccessibleContext *>( xAccParent.get() ); + if ( pAccParentImpl ) + { + pParent = pAccParentImpl->GetFrame(); + } + } + } + } + } + + return pParent; +} + +} // namespace sw::access + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobj.hxx b/sw/source/core/access/accfrmobj.hxx new file mode 100644 index 000000000..cb5fdfb7b --- /dev/null +++ b/sw/source/core/access/accfrmobj.hxx @@ -0,0 +1,89 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJ_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJ_HXX + +#include <vcl/vclptr.hxx> + +class SwAccessibleMap; +class SwFrame; +class SdrObject; +namespace vcl { class Window; } +class SwRect; + +namespace sw::access { + +class SwAccessibleChild +{ + public: + SwAccessibleChild(); + ~SwAccessibleChild(); + explicit SwAccessibleChild( const SdrObject* pDrawObj ); + explicit SwAccessibleChild( const SwFrame* pFrame ); + explicit SwAccessibleChild( vcl::Window* pWindow ); + SwAccessibleChild( const SwFrame* pFrame, + const SdrObject* pDrawObj, + vcl::Window* pWindow ); + + SwAccessibleChild(SwAccessibleChild const &) = default; + SwAccessibleChild(SwAccessibleChild &&) = default; + SwAccessibleChild & operator =(SwAccessibleChild const &) = default; + SwAccessibleChild & operator =(SwAccessibleChild &&) = default; + + SwAccessibleChild& operator=( const SdrObject* pDrawObj ); + SwAccessibleChild& operator=( const SwFrame* pFrame ); + SwAccessibleChild& operator=( vcl::Window* pWindow ); + + bool operator==( const SwAccessibleChild& r ) const; + + bool IsValid() const; + + const SwFrame* GetSwFrame() const { return mpFrame; } + const SdrObject* GetDrawObject() const { return mpDrawObj; } + vcl::Window* GetWindow() const { return mpWindow; } + + const SwFrame* GetParent( const bool bInPagePreview ) const; + + bool IsAccessible( bool bPagePreview ) const; + bool IsBoundAsChar() const; + + bool IsVisibleChildrenOnly() const; + SwRect GetBox( const SwAccessibleMap& rAccMap ) const; + SwRect GetBounds( const SwAccessibleMap& rAccMap ) const; + + /** indicating, if accessible child is included even, if the corresponding + object is not visible. */ + bool AlwaysIncludeAsChild() const; + + private: + const SwFrame* mpFrame; + const SdrObject* mpDrawObj; + VclPtr<vcl::Window> mpWindow; + + void Init( const SdrObject* pDrawObj ); + void Init( const SwFrame* pFrame ); + void Init( vcl::Window* pWindow ); +}; + +} // eof of namespace sw::access + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobjmap.cxx b/sw/source/core/access/accfrmobjmap.cxx new file mode 100644 index 000000000..5da8dad1a --- /dev/null +++ b/sw/source/core/access/accfrmobjmap.cxx @@ -0,0 +1,149 @@ +/* -*- 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 . + */ + +#include "accfrmobjmap.hxx" +#include <accmap.hxx> +#include "acccontext.hxx" + +#include <viewsh.hxx> +#include <doc.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <pagefrm.hxx> +#include <sortedobjs.hxx> +#include <anchoredobject.hxx> + +#include <svx/svdobj.hxx> + +using namespace sw::access; + +SwAccessibleChildMap::SwAccessibleChildMap( const SwRect& rVisArea, + const SwFrame& rFrame, + SwAccessibleMap& rAccMap ) + : mnHellId( rAccMap.GetShell()->GetDoc()->getIDocumentDrawModelAccess().GetHellId() ) + , mnControlsId( rAccMap.GetShell()->GetDoc()->getIDocumentDrawModelAccess().GetControlsId() ) +{ + const bool bVisibleChildrenOnly = SwAccessibleChild( &rFrame ).IsVisibleChildrenOnly(); + + sal_uInt32 nPos = 0; + SwAccessibleChild aLower( rFrame.GetLower() ); + while( aLower.GetSwFrame() ) + { + if ( !bVisibleChildrenOnly || + aLower.AlwaysIncludeAsChild() || + aLower.GetBox( rAccMap ).Overlaps( rVisArea ) ) + { + insert( nPos++, SwAccessibleChildMapKey::TEXT, aLower ); + } + + aLower = aLower.GetSwFrame()->GetNext(); + } + + if ( rFrame.IsPageFrame() ) + { + OSL_ENSURE( bVisibleChildrenOnly, "page frame within tab frame???" ); + const SwPageFrame *pPgFrame = + static_cast< const SwPageFrame * >( &rFrame ); + const SwSortedObjs *pObjs = pPgFrame->GetSortedObjs(); + if ( pObjs ) + { + for(const SwAnchoredObject* pObj : *pObjs) + { + aLower = pObj->GetDrawObj(); + if ( aLower.GetBox( rAccMap ).Overlaps( rVisArea ) ) + { + insert( aLower.GetDrawObject(), aLower ); + } + } + } + } + else if( rFrame.IsTextFrame() ) + { + const SwSortedObjs *pObjs = rFrame.GetDrawObjs(); + if ( pObjs ) + { + for(const SwAnchoredObject* pObj : *pObjs) + { + aLower = pObj->GetDrawObj(); + if ( aLower.IsBoundAsChar() && + ( !bVisibleChildrenOnly || + aLower.AlwaysIncludeAsChild() || + aLower.GetBox( rAccMap ).Overlaps( rVisArea ) ) ) + { + insert( aLower.GetDrawObject(), aLower ); + } + } + } + + { + ::rtl::Reference < SwAccessibleContext > xAccImpl = + rAccMap.GetContextImpl( &rFrame, false ); + if( xAccImpl.is() ) + { + SwAccessibleContext* pAccImpl = xAccImpl.get(); + if ( pAccImpl && + pAccImpl->HasAdditionalAccessibleChildren() ) + { + std::vector< vcl::Window* > aAdditionalChildren; + pAccImpl->GetAdditionalAccessibleChildren( &aAdditionalChildren ); + + sal_Int32 nCounter( 0 ); + for ( const auto& rpChild : aAdditionalChildren ) + { + aLower = rpChild; + insert( ++nCounter, SwAccessibleChildMapKey::XWINDOW, aLower ); + } + } + } + } + } +} + +std::pair< SwAccessibleChildMap::iterator, bool > SwAccessibleChildMap::insert( + const sal_uInt32 nPos, + const SwAccessibleChildMapKey::LayerId eLayerId, + const SwAccessibleChild& rLower ) +{ + SwAccessibleChildMapKey aKey( eLayerId, nPos ); + return emplace( aKey, rLower ); +} + +std::pair< SwAccessibleChildMap::iterator, bool > SwAccessibleChildMap::insert( + const SdrObject *pObj, + const SwAccessibleChild& rLower ) +{ + const SdrLayerID nLayer = pObj->GetLayer(); + SwAccessibleChildMapKey::LayerId eLayerId = + (mnHellId == nLayer) + ? SwAccessibleChildMapKey::HELL + : ( (mnControlsId == nLayer) + ? SwAccessibleChildMapKey::CONTROLS + : SwAccessibleChildMapKey::HEAVEN ); + SwAccessibleChildMapKey aKey( eLayerId, pObj->GetOrdNum() ); + return emplace( aKey, rLower ); +} + +bool SwAccessibleChildMap::IsSortingRequired( const SwFrame& rFrame ) +{ + return ( rFrame.IsPageFrame() && + static_cast< const SwPageFrame& >( rFrame ).GetSortedObjs() ) || + ( rFrame.IsTextFrame() && + rFrame.GetDrawObjs() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobjmap.hxx b/sw/source/core/access/accfrmobjmap.hxx new file mode 100644 index 000000000..ed4ecafec --- /dev/null +++ b/sw/source/core/access/accfrmobjmap.hxx @@ -0,0 +1,126 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJMAP_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJMAP_HXX + +#include <tools/gen.hxx> +#include <svx/svdtypes.hxx> +#include "accfrmobj.hxx" +#include <map> + +class SwAccessibleMap; +class SwRect; +class SwFrame; +class SdrObject; + +class SwAccessibleChildMapKey +{ +public: + enum LayerId { INVALID, HELL, TEXT, HEAVEN, CONTROLS, XWINDOW }; + + SwAccessibleChildMapKey() + : m_eLayerId( INVALID ) + , m_nOrdNum( 0 ) + , m_nPosNum( 0, 0 ) + {} + + SwAccessibleChildMapKey( LayerId eId, sal_uInt32 nOrd ) + : m_eLayerId( eId ) + , m_nOrdNum( nOrd ) + , m_nPosNum( 0, 0 ) + {} + + bool operator()( const SwAccessibleChildMapKey& r1, + const SwAccessibleChildMapKey& r2 ) const + { + if(r1.m_eLayerId == r2.m_eLayerId) + { + if(r1.m_nPosNum == r2.m_nPosNum) + return r1.m_nOrdNum < r2.m_nOrdNum; + else + { + if(r1.m_nPosNum.getY() == r2.m_nPosNum.getY()) + return r1.m_nPosNum.getX() < r2.m_nPosNum.getX(); + else + return r1.m_nPosNum.getY() < r2.m_nPosNum.getY(); + } + } + else + return r1.m_eLayerId < r2.m_eLayerId; + } + + /* MT: Need to get this position parameter stuff in dev300 somehow... + //This methods are used to insert an object to the map, adding a position parameter. + std::pair< iterator, bool > insert( sal_uInt32 nOrd, Point nPos, + const SwFrameOrObj& rLower ); + std::pair< iterator, bool > insert( const SdrObject *pObj, + const SwFrameOrObj& rLower, + const SwDoc *pDoc, + Point nPos); + */ + +private: + LayerId m_eLayerId; + sal_uInt32 m_nOrdNum; + Point m_nPosNum; +}; + + +class SwAccessibleChildMap +{ +public: + typedef SwAccessibleChildMapKey key_type; + typedef sw::access::SwAccessibleChild mapped_type; + typedef std::pair<const key_type,mapped_type> value_type; + typedef SwAccessibleChildMapKey key_compare; + typedef std::map<key_type,mapped_type,key_compare>::iterator iterator; + typedef std::map<key_type,mapped_type,key_compare>::const_iterator const_iterator; + typedef std::map<key_type,mapped_type,key_compare>::const_reverse_iterator const_reverse_iterator; + +private: + const SdrLayerID mnHellId; + const SdrLayerID mnControlsId; + std::map<key_type,mapped_type,key_compare> maMap; + + std::pair< iterator, bool > insert( const sal_uInt32 nPos, + const SwAccessibleChildMapKey::LayerId eLayerId, + const sw::access::SwAccessibleChild& rLower ); + std::pair< iterator, bool > insert( const SdrObject* pObj, + const sw::access::SwAccessibleChild& rLower ); + +public: + SwAccessibleChildMap( const SwRect& rVisArea, + const SwFrame& rFrame, + SwAccessibleMap& rAccMap ); + + static bool IsSortingRequired( const SwFrame& rFrame ); + + const_iterator cbegin() const { return maMap.cbegin(); } + const_iterator cend() const { return maMap.cend(); } + const_reverse_iterator crbegin() const { return maMap.crbegin(); } + const_reverse_iterator crend() const { return maMap.crend(); } + + template<class... Args> + std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobjslist.cxx b/sw/source/core/access/accfrmobjslist.cxx new file mode 100644 index 000000000..c62987798 --- /dev/null +++ b/sw/source/core/access/accfrmobjslist.cxx @@ -0,0 +1,163 @@ +/* -*- 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 . + */ + +#include "accfrmobjslist.hxx" +#include <accmap.hxx> +#include "acccontext.hxx" + +#include <pagefrm.hxx> +#include <sortedobjs.hxx> +#include <anchoredobject.hxx> + +using namespace ::sw::access; + +SwAccessibleChildSList_const_iterator::SwAccessibleChildSList_const_iterator( + const SwAccessibleChildSList& rLst, + SwAccessibleMap& rAccMap ) + : m_rList( rLst ), + m_aCurr( m_rList.GetFrame().GetLower() ), + m_nNextObj( 0 ) +{ + if( !m_aCurr.GetSwFrame() ) + { + const SwFrame& rFrame = m_rList.GetFrame(); + if( rFrame.IsPageFrame() ) + { + const SwPageFrame& rPgFrame = static_cast< const SwPageFrame& >( rFrame ); + const SwSortedObjs *pObjs = rPgFrame.GetSortedObjs(); + if( pObjs && pObjs->size() ) + { + m_aCurr = (*pObjs)[m_nNextObj++]->GetDrawObj(); + } + } + else if( rFrame.IsTextFrame() ) + { + const SwSortedObjs *pObjs = rFrame.GetDrawObjs(); + if ( pObjs && pObjs->size() ) + { + m_aCurr = (*pObjs)[m_nNextObj++]->GetDrawObj(); + while( m_aCurr.IsValid() && !m_aCurr.IsBoundAsChar() ) + { + m_aCurr = (m_nNextObj < pObjs->size()) + ? (*pObjs)[m_nNextObj++]->GetDrawObj() + : static_cast< const SdrObject *>( nullptr ); + } + } + if ( !m_aCurr.IsValid() ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl = + rAccMap.GetContextImpl( &rFrame, false ); + if( xAccImpl.is() ) + { + SwAccessibleContext* pAccImpl = xAccImpl.get(); + m_aCurr = SwAccessibleChild( pAccImpl->GetAdditionalAccessibleChild( 0 ) ); + ++m_nNextObj; + } + } + } + } + + if( m_rList.IsVisibleChildrenOnly() ) + { + // Find the first visible + while( m_aCurr.IsValid() && + !m_aCurr.AlwaysIncludeAsChild() && + !m_aCurr.GetBox( rAccMap ).Overlaps( m_rList.GetVisArea() ) ) + { + next(); + } + } +} + +SwAccessibleChildSList_const_iterator& SwAccessibleChildSList_const_iterator::next() +{ + bool bNextTaken( true ); + if( m_aCurr.GetDrawObject() || m_aCurr.GetWindow() ) + { + bNextTaken = false; + } + else if( m_aCurr.GetSwFrame() ) + { + m_aCurr = m_aCurr.GetSwFrame()->GetNext(); + if( !m_aCurr.GetSwFrame() ) + { + bNextTaken = false; + } + } + + if( !bNextTaken ) + { + const SwFrame& rFrame = m_rList.GetFrame(); + if( rFrame.IsPageFrame() ) + { + const SwPageFrame& rPgFrame = static_cast< const SwPageFrame& >( rFrame ); + const SwSortedObjs *pObjs = rPgFrame.GetSortedObjs(); + m_aCurr = ( pObjs && m_nNextObj < pObjs->size() ) + ? (*pObjs)[m_nNextObj++]->GetDrawObj() + : static_cast< const SdrObject *>( nullptr ); + } + else if( rFrame.IsTextFrame() ) + { + const SwSortedObjs* pObjs = rFrame.GetDrawObjs(); + const size_t nObjsCount = pObjs ? pObjs->size() : 0; + m_aCurr = ( pObjs && m_nNextObj < nObjsCount ) + ? (*pObjs)[m_nNextObj++]->GetDrawObj() + : static_cast< const SdrObject *>( nullptr ); + while( m_aCurr.IsValid() && !m_aCurr.IsBoundAsChar() ) + { + m_aCurr = ( m_nNextObj < nObjsCount ) + ? (*pObjs)[m_nNextObj++]->GetDrawObj() + : static_cast< const SdrObject *>( nullptr ); + } + if ( !m_aCurr.IsValid() ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl = + m_rList.GetAccMap().GetContextImpl( &rFrame, false ); + if( xAccImpl.is() ) + { + SwAccessibleContext* pAccImpl = xAccImpl.get(); + m_aCurr = SwAccessibleChild( pAccImpl->GetAdditionalAccessibleChild( m_nNextObj - nObjsCount ) ); + ++m_nNextObj; + } + } + } + } + + return *this; +} + +SwAccessibleChildSList_const_iterator& SwAccessibleChildSList_const_iterator::next_visible() +{ + next(); + while( m_aCurr.IsValid() && + !m_aCurr.AlwaysIncludeAsChild() && + !m_aCurr.GetBox( m_rList.GetAccMap() ).Overlaps( m_rList.GetVisArea() ) ) + { + next(); + } + + return *this; +} + +SwAccessibleChildSList_const_iterator& SwAccessibleChildSList_const_iterator::operator++() +{ + return m_rList.IsVisibleChildrenOnly() ? next_visible() : next(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobjslist.hxx b/sw/source/core/access/accfrmobjslist.hxx new file mode 100644 index 000000000..9df293d71 --- /dev/null +++ b/sw/source/core/access/accfrmobjslist.hxx @@ -0,0 +1,131 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJSLIST_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJSLIST_HXX + +#include "accfrmobj.hxx" +#include <swrect.hxx> + +class SwAccessibleMap; +class SwAccessibleChildSList; + +class SwAccessibleChildSList_const_iterator +{ +private: + friend class SwAccessibleChildSList; + + const SwAccessibleChildSList& m_rList; // The frame we are iterating over + sw::access::SwAccessibleChild m_aCurr; // The current object + size_t m_nNextObj; // The index of the current sdr object + + SwAccessibleChildSList_const_iterator( const SwAccessibleChildSList& rLst ) + : m_rList( rLst ) + , m_nNextObj( 0 ) + {} + + SwAccessibleChildSList_const_iterator( const SwAccessibleChildSList& rLst, + SwAccessibleMap& rAccMap ); + + SwAccessibleChildSList_const_iterator& next(); + SwAccessibleChildSList_const_iterator& next_visible(); + +public: + bool operator==( const SwAccessibleChildSList_const_iterator& r ) const + { + return m_aCurr == r.m_aCurr; + } + + bool operator!=( + const SwAccessibleChildSList_const_iterator& r ) const + { + return !(*this == r); + } + + SwAccessibleChildSList_const_iterator& operator++(); + + const sw::access::SwAccessibleChild& operator*() const + { + return m_aCurr; + } +}; + +// An iterator to iterate over a frame's child in any order +class SwAccessibleChildSList +{ + const SwRect maVisArea; + const SwFrame& mrFrame; + const bool mbVisibleChildrenOnly; + SwAccessibleMap& mrAccMap; + +public: + typedef SwAccessibleChildSList_const_iterator const_iterator; + + SwAccessibleChildSList( const SwFrame& rFrame, + SwAccessibleMap& rAccMap ) + : maVisArea() + , mrFrame( rFrame ) + , mbVisibleChildrenOnly( false ) + , mrAccMap( rAccMap ) + {} + + SwAccessibleChildSList( const SwRect& rVisArea, + const SwFrame& rFrame, + SwAccessibleMap& rAccMap ) + : maVisArea( rVisArea ) + , mrFrame( rFrame ) + , mbVisibleChildrenOnly( sw::access::SwAccessibleChild( &rFrame ).IsVisibleChildrenOnly() ) + , mrAccMap( rAccMap ) + { + } + + const_iterator begin() const + { + return SwAccessibleChildSList_const_iterator( *this, mrAccMap ); + } + + const_iterator end() const + { + return SwAccessibleChildSList_const_iterator( *this ); + } + + const SwFrame& GetFrame() const + { + return mrFrame; + } + + bool IsVisibleChildrenOnly() const + { + return mbVisibleChildrenOnly; + } + + const SwRect& GetVisArea() const + { + return maVisArea; + } + + SwAccessibleMap& GetAccMap() const + { + return mrAccMap; + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accgraphic.cxx b/sw/source/core/access/accgraphic.cxx new file mode 100644 index 000000000..d6d115d78 --- /dev/null +++ b/sw/source/core/access/accgraphic.cxx @@ -0,0 +1,75 @@ +/* -*- 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 . + */ + +#include <vcl/svapp.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <flyfrm.hxx> +#include <fmturl.hxx> +#include "accgraphic.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + +SwAccessibleGraphic::SwAccessibleGraphic( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFlyFrame* pFlyFrame ) : + SwAccessibleNoTextFrame( pInitMap, AccessibleRole::GRAPHIC, pFlyFrame ) +{ +} + +SwAccessibleGraphic::~SwAccessibleGraphic() +{ +} + +OUString SAL_CALL SwAccessibleGraphic::getImplementationName() +{ + return "com.sun.star.comp.Writer.SwAccessibleGraphic"; +} + +sal_Bool SAL_CALL SwAccessibleGraphic::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +Sequence< OUString > SAL_CALL SwAccessibleGraphic::getSupportedServiceNames() +{ + return { "com.sun.star.text.AccessibleTextGraphicObject", sAccessibleServiceName }; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessibleGraphic::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// Return this object's role. +sal_Int16 SAL_CALL SwAccessibleGraphic::getAccessibleRole() +{ + SolarMutexGuard g; + + SwFormatURL aURL( static_cast<const SwLayoutFrame*>(GetFrame())->GetFormat()->GetURL() ); + + if (aURL.GetMap()) + return AccessibleRole::IMAGE_MAP; + return AccessibleRole::GRAPHIC; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accgraphic.hxx b/sw/source/core/access/accgraphic.hxx new file mode 100644 index 000000000..dc21201bc --- /dev/null +++ b/sw/source/core/access/accgraphic.hxx @@ -0,0 +1,57 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCGRAPHIC_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCGRAPHIC_HXX + +#include "accnotextframe.hxx" + +class SwAccessibleGraphic : public SwAccessibleNoTextFrame +{ +protected: + virtual ~SwAccessibleGraphic() override; + +public: + SwAccessibleGraphic(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFlyFrame *pFlyFrame ); + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + /// Return this object's role. + virtual sal_Int16 SAL_CALL getAccessibleRole() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accheaderfooter.cxx b/sw/source/core/access/accheaderfooter.cxx new file mode 100644 index 000000000..d07b2c802 --- /dev/null +++ b/sw/source/core/access/accheaderfooter.cxx @@ -0,0 +1,112 @@ +/* -*- 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 . + */ + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <vcl/svapp.hxx> +#include <hffrm.hxx> +#include "accheaderfooter.hxx" +#include <strings.hrc> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + +constexpr OUStringLiteral sImplementationNameHeader + = u"com.sun.star.comp.Writer.SwAccessibleHeaderView"; +constexpr OUStringLiteral sImplementationNameFooter + = u"com.sun.star.comp.Writer.SwAccessibleFooterView"; + +SwAccessibleHeaderFooter::SwAccessibleHeaderFooter( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwHeaderFrame* pHdFrame ) : + SwAccessibleContext( pInitMap, AccessibleRole::HEADER, pHdFrame ) +{ + OUString sArg( OUString::number( pHdFrame->GetPhyPageNum() ) ); + SetName( GetResource( STR_ACCESS_HEADER_NAME, &sArg ) ); +} + +SwAccessibleHeaderFooter::SwAccessibleHeaderFooter( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFooterFrame* pFtFrame ) : + SwAccessibleContext( pInitMap, AccessibleRole::FOOTER, pFtFrame ) +{ + OUString sArg( OUString::number( pFtFrame->GetPhyPageNum() ) ); + SetName( GetResource( STR_ACCESS_FOOTER_NAME, &sArg ) ); +} + +SwAccessibleHeaderFooter::~SwAccessibleHeaderFooter() +{ +} + +OUString SAL_CALL SwAccessibleHeaderFooter::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + TranslateId pResId = AccessibleRole::HEADER == GetRole() + ? STR_ACCESS_HEADER_DESC + : STR_ACCESS_FOOTER_DESC ; + + OUString sArg( GetFormattedPageNumber() ); + + return GetResource(pResId, &sArg); +} + +OUString SAL_CALL SwAccessibleHeaderFooter::getImplementationName() +{ + if( AccessibleRole::HEADER == GetRole() ) + return sImplementationNameHeader; + else + return sImplementationNameFooter; +} + +sal_Bool SAL_CALL SwAccessibleHeaderFooter::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +Sequence< OUString > SAL_CALL SwAccessibleHeaderFooter::getSupportedServiceNames() +{ + return { (AccessibleRole::HEADER == GetRole())?OUString("com.sun.star.text.AccessibleHeaderView"):OUString("com.sun.star.text.AccessibleFooterView"), + sAccessibleServiceName }; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessibleHeaderFooter::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +sal_Int32 SAL_CALL SwAccessibleHeaderFooter::getBackground() +{ + Reference< XAccessible > xParent = getAccessibleParent(); + if (xParent.is()) + { + Reference< XAccessibleComponent > xAccContext (xParent,UNO_QUERY); + if(xAccContext.is()) + { + return xAccContext->getBackground(); + } + } + return SwAccessibleContext::getBackground(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accheaderfooter.hxx b/sw/source/core/access/accheaderfooter.hxx new file mode 100644 index 000000000..821ed9883 --- /dev/null +++ b/sw/source/core/access/accheaderfooter.hxx @@ -0,0 +1,64 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHEADERFOOTER_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHEADERFOOTER_HXX + +#include "acccontext.hxx" + +class SwHeaderFrame; +class SwFooterFrame; + +class SwAccessibleHeaderFooter : public SwAccessibleContext +{ +protected: + virtual ~SwAccessibleHeaderFooter() override; + +public: + SwAccessibleHeaderFooter(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwHeaderFrame* pHdFrame); + SwAccessibleHeaderFooter(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFooterFrame* pFtFrame); + + // XAccessibleContext + + /// Return this object's description. + virtual OUString SAL_CALL getAccessibleDescription() override; + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. */ + virtual OUString SAL_CALL getImplementationName() override; + + /** Return whether the specified service is supported by this class. */ + virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. */ + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId() override; + // XAccessibleComponent + sal_Int32 SAL_CALL getBackground() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acchyperlink.cxx b/sw/source/core/access/acchyperlink.cxx new file mode 100644 index 000000000..76a2721ec --- /dev/null +++ b/sw/source/core/access/acchyperlink.cxx @@ -0,0 +1,236 @@ +/* -*- 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 . + */ + +#include <comphelper/accessiblekeybindinghelper.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/frame/XDesktop.hpp> +#include <com/sun/star/document/XLinkTargetSupplier.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <swurl.hxx> +#include <vcl/keycodes.hxx> +#include <vcl/svapp.hxx> +#include <txtinet.hxx> +#include "accpara.hxx" +#include "acchyperlink.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using ::com::sun::star::lang::IndexOutOfBoundsException; + +SwAccessibleHyperlink::SwAccessibleHyperlink(const SwTextAttr & rTextAttr, + SwAccessibleParagraph & rAccPara, + sal_Int32 const nStt, sal_Int32 const nEnd) + : m_pHyperlink(const_cast<SwFormatINetFormat*>(&rTextAttr.GetINetFormat())) + , m_xParagraph(&rAccPara) + , m_nStartIndex( nStt ) + , m_nEndIndex( nEnd ) +{ + StartListening(m_pHyperlink->GetNotifier()); +} + +SwAccessibleHyperlink::~SwAccessibleHyperlink() +{ + Invalidate(); // with SolarMutex! +} + +// when the pool item dies, invalidate! this is the only reason for Listener... +void SwAccessibleHyperlink::Notify(SfxHint const& rHint) +{ + if (rHint.GetId() == SfxHintId::Dying) + { + Invalidate(); + } +} + +// both the parent SwAccessibleParagraph and the pool-item must be valid +const SwFormatINetFormat *SwAccessibleHyperlink::GetTextAttr() const +{ + return (m_xParagraph.is() && m_xParagraph->GetMap()) + ? m_pHyperlink + : nullptr; +} + +// XAccessibleAction +sal_Int32 SAL_CALL SwAccessibleHyperlink::getAccessibleActionCount() +{ + return isValid() ? 1 : 0; +} + +sal_Bool SAL_CALL SwAccessibleHyperlink::doAccessibleAction( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + bool bRet = false; + + if(nIndex != 0) + throw lang::IndexOutOfBoundsException(); + SwFormatINetFormat const*const pINetFormat = GetTextAttr(); + if (pINetFormat && !pINetFormat->GetValue().isEmpty()) + { + SwViewShell *pVSh = m_xParagraph->GetShell(); + if (pVSh) + { + LoadURL(*pVSh, pINetFormat->GetValue(), LoadUrlFlags::NONE, + pINetFormat->GetTargetFrame()); + const SwTextINetFormat *const pTextAttr = pINetFormat->GetTextINetFormat(); + if (pTextAttr) + { + const_cast<SwTextINetFormat*>(pTextAttr)->SetVisited(true); + const_cast<SwTextINetFormat*>(pTextAttr)->SetVisitedValid(true); + } + bRet = true; + } + } + + return bRet; +} + +OUString SAL_CALL SwAccessibleHyperlink::getAccessibleActionDescription( + sal_Int32 nIndex ) +{ + if(nIndex != 0) + throw lang::IndexOutOfBoundsException(); + + SolarMutexGuard g; + if (SwFormatINetFormat const*const pINetFormat = GetTextAttr()) + { + return pINetFormat->GetValue(); + } + + return OUString(); +} + +uno::Reference< XAccessibleKeyBinding > SAL_CALL + SwAccessibleHyperlink::getAccessibleActionKeyBinding( sal_Int32 ) +{ + uno::Reference< XAccessibleKeyBinding > xKeyBinding; + + if( isValid() ) + { + rtl::Reference<::comphelper::OAccessibleKeyBindingHelper> pKeyBindingHelper = + new ::comphelper::OAccessibleKeyBindingHelper(); + xKeyBinding = pKeyBindingHelper; + + awt::KeyStroke aKeyStroke; + aKeyStroke.Modifiers = 0; + aKeyStroke.KeyCode = KEY_RETURN; + aKeyStroke.KeyChar = 0; + aKeyStroke.KeyFunc = 0; + pKeyBindingHelper->AddKeyBinding( aKeyStroke ); + } + + return xKeyBinding; +} + +// XAccessibleHyperlink +uno::Any SAL_CALL SwAccessibleHyperlink::getAccessibleActionAnchor( + sal_Int32 nIndex) +{ + SolarMutexGuard g; + + uno::Any aRet; + if(nIndex != 0) + throw lang::IndexOutOfBoundsException(); + OUString text( m_xParagraph->GetString() ); + OUString retText = text.copy(m_nStartIndex, m_nEndIndex - m_nStartIndex); + aRet <<= retText; + return aRet; +} + +uno::Any SAL_CALL SwAccessibleHyperlink::getAccessibleActionObject( + sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + if(nIndex != 0) + throw lang::IndexOutOfBoundsException(); + OUString retText; + if (SwFormatINetFormat const*const pINetFormat = GetTextAttr()) + { + retText = pINetFormat->GetValue(); + } + uno::Any aRet; + aRet <<= retText; + return aRet; +} + +sal_Int32 SAL_CALL SwAccessibleHyperlink::getStartIndex() +{ + return m_nStartIndex; +} + +sal_Int32 SAL_CALL SwAccessibleHyperlink::getEndIndex() +{ + return m_nEndIndex; +} + +sal_Bool SAL_CALL SwAccessibleHyperlink::isValid( ) +{ + SolarMutexGuard aGuard; + if (m_xParagraph.is()) + { + if (SwFormatINetFormat const*const pINetFormat = GetTextAttr()) + { + OUString const sText(pINetFormat->GetValue()); + sal_Int32 nPos = sText.indexOf("#"); + if (nPos==0)//document link + { + uno::Reference< lang::XMultiServiceFactory > xFactory( ::comphelper::getProcessServiceFactory() ); + if( ! xFactory.is() ) + return false; + uno::Reference< css::frame::XDesktop > xDesktop( xFactory->createInstance( "com.sun.star.frame.Desktop" ), + uno::UNO_QUERY ); + if( !xDesktop.is() ) + return false; + uno::Reference< lang::XComponent > xComp = xDesktop->getCurrentComponent(); + if( !xComp.is() ) + return false; + uno::Reference< css::document::XLinkTargetSupplier > xLTS(xComp, uno::UNO_QUERY); + if ( !xLTS.is()) + return false; + + uno::Reference< css::container::XNameAccess > xLinks = xLTS->getLinks(); + uno::Reference< css::container::XNameAccess > xSubLinks; + const uno::Sequence< OUString > aNames( xLinks->getElementNames() ); + + for( const OUString& aLink : aNames ) + { + uno::Any aAny = xLinks->getByName( aLink ); + aAny >>= xSubLinks; + if (xSubLinks->hasByName(sText.copy(1)) ) + return true; + } + } + else//internet + return true; + } + }//xpara valid + return false; +} + +void SwAccessibleHyperlink::Invalidate() +{ + SolarMutexGuard aGuard; + m_xParagraph = nullptr; + m_pHyperlink = nullptr; + EndListeningAll(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acchyperlink.hxx b/sw/source/core/access/acchyperlink.hxx new file mode 100644 index 000000000..68116d99d --- /dev/null +++ b/sw/source/core/access/acchyperlink.hxx @@ -0,0 +1,75 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHYPERLINK_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHYPERLINK_HXX + +#include <com/sun/star/accessibility/XAccessibleHyperlink.hpp> +#include <rtl/ref.hxx> +#include <cppuhelper/implbase.hxx> +#include <svl/listener.hxx> + +class SwFormatINetFormat; +class SwAccessibleParagraph; +class SwTextAttr; + +class SwAccessibleHyperlink + : public ::cppu::WeakImplHelper<css::accessibility::XAccessibleHyperlink> + , public SvtListener +{ + friend class SwAccessibleParagraph; + friend class SwAccessibleHyperTextData; + SwFormatINetFormat * m_pHyperlink; + ::rtl::Reference< SwAccessibleParagraph > m_xParagraph; + sal_Int32 m_nStartIndex; + sal_Int32 m_nEndIndex; + + SwAccessibleHyperlink(const SwTextAttr &, + SwAccessibleParagraph &, + sal_Int32 nStt, sal_Int32 nEnd ); + virtual ~SwAccessibleHyperlink() override; + + const SwFormatINetFormat* GetTextAttr() const; + void Invalidate(); + + virtual void Notify(SfxHint const& rHint) override; + +public: + // XAccessibleAction + virtual sal_Int32 SAL_CALL getAccessibleActionCount() override; + virtual sal_Bool SAL_CALL doAccessibleAction( sal_Int32 nIndex ) override; + virtual OUString SAL_CALL getAccessibleActionDescription( + sal_Int32 nIndex ) override; + virtual css::uno::Reference< + css::accessibility::XAccessibleKeyBinding > SAL_CALL + getAccessibleActionKeyBinding( sal_Int32 nIndex ) override; + + // XAccessibleHyperlink + virtual css::uno::Any SAL_CALL getAccessibleActionAnchor( + sal_Int32 nIndex ) override; + virtual css::uno::Any SAL_CALL getAccessibleActionObject( + sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getStartIndex() override; + virtual sal_Int32 SAL_CALL getEndIndex() override; + virtual sal_Bool SAL_CALL isValid( ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acchypertextdata.cxx b/sw/source/core/access/acchypertextdata.cxx new file mode 100644 index 000000000..918bcc631 --- /dev/null +++ b/sw/source/core/access/acchypertextdata.cxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +#include "acchyperlink.hxx" +#include "acchypertextdata.hxx" + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +SwAccessibleHyperTextData::SwAccessibleHyperTextData() +{ +} + +SwAccessibleHyperTextData::~SwAccessibleHyperTextData() +{ + iterator aIter = begin(); + while( aIter != end() ) + { + Reference < XAccessibleHyperlink > xTmp = (*aIter).second; + if( xTmp.is() ) + { + SwAccessibleHyperlink *pTmp = + static_cast< SwAccessibleHyperlink * >( xTmp.get() ); + pTmp->Invalidate(); + } + ++aIter; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acchypertextdata.hxx b/sw/source/core/access/acchypertextdata.hxx new file mode 100644 index 000000000..01833885a --- /dev/null +++ b/sw/source/core/access/acchypertextdata.hxx @@ -0,0 +1,54 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHYPERTEXTDATA_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHYPERTEXTDATA_HXX + +#include <cppuhelper/weakref.hxx> +#include <map> + +class SwTextAttr; +namespace com::sun::star::accessibility { + class XAccessibleHyperlink; +} + +class SwAccessibleHyperTextData +{ +public: + typedef const SwTextAttr * key_type; + typedef css::uno::WeakReference< css::accessibility::XAccessibleHyperlink > mapped_type; + typedef std::pair<const key_type,mapped_type> value_type; + typedef std::less< const SwTextAttr * > key_compare; + typedef std::map<key_type,mapped_type,key_compare>::iterator iterator; +private: + std::map<key_type,mapped_type,key_compare> maMap; +public: + SwAccessibleHyperTextData(); + ~SwAccessibleHyperTextData(); + + iterator begin() { return maMap.begin(); } + iterator end() { return maMap.end(); } + iterator find(const key_type& key) { return maMap.find(key); } + template<class... Args> + std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accmap.cxx b/sw/source/core/access/accmap.cxx new file mode 100644 index 000000000..d5c30b9d9 --- /dev/null +++ b/sw/source/core/access/accmap.cxx @@ -0,0 +1,3440 @@ +/* -*- 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 . + */ + +#include <rtl/ref.hxx> +#include <cppuhelper/weakref.hxx> +#include <vcl/window.hxx> +#include <svx/svdmodel.hxx> +#include <svx/unomod.hxx> +#include <algorithm> +#include <map> +#include <unordered_map> +#include <list> +#include <vector> +#include <accmap.hxx> +#include "acccontext.hxx" +#include "accdoc.hxx" +#include <strings.hrc> +#include "accpreview.hxx" +#include "accpage.hxx" +#include "accpara.hxx" +#include "accheaderfooter.hxx" +#include "accfootnote.hxx" +#include "acctextframe.hxx" +#include "accgraphic.hxx" +#include "accembedded.hxx" +#include "acccell.hxx" +#include "acctable.hxx" +#include <fesh.hxx> +#include <istype.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <hffrm.hxx> +#include <ftnfrm.hxx> +#include <cellfrm.hxx> +#include <tabfrm.hxx> +#include <pagefrm.hxx> +#include <flyfrm.hxx> +#include <ndtyp.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <svx/AccessibleShapeInfo.hxx> +#include <svx/ShapeTypeHandler.hxx> +#include <svx/SvxShapeTypes.hxx> +#include <svx/svdpage.hxx> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/document/XShapeEventBroadcaster.hpp> +#include <cppuhelper/implbase.hxx> +#include <comphelper/interfacecontainer3.hxx> +#include <pagepreviewlayout.hxx> +#include <dcontact.hxx> +#include <svx/svdmark.hxx> +#include <doc.hxx> +#include <drawdoc.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <dflyobj.hxx> +#include <prevwpage.hxx> +#include <calbck.hxx> +#include <undobj.hxx> +#include <tools/diagnose_ex.h> +#include <tools/debug.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::sw::access; + +class SwAccessibleContextMap_Impl +{ +public: + typedef const SwFrame * key_type; + typedef uno::WeakReference < XAccessible > mapped_type; + typedef std::pair<const key_type,mapped_type> value_type; + typedef std::map<key_type, mapped_type>::iterator iterator; + typedef std::map<key_type, mapped_type>::const_iterator const_iterator; +private: + std::map <key_type, mapped_type> maMap; +public: + +#if OSL_DEBUG_LEVEL > 0 + bool mbLocked; +#endif + + SwAccessibleContextMap_Impl() +#if OSL_DEBUG_LEVEL > 0 + : mbLocked( false ) +#endif + {} + + iterator begin() { return maMap.begin(); } + iterator end() { return maMap.end(); } + bool empty() const { return maMap.empty(); } + void clear() { maMap.clear(); } + iterator find(const key_type& key) { return maMap.find(key); } + template<class... Args> + std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); } + iterator erase(const_iterator const & pos) { return maMap.erase(pos); } +}; + +namespace { + +class SwDrawModellListener_Impl : public SfxListener, + public ::cppu::WeakImplHelper< document::XShapeEventBroadcaster > +{ + mutable ::osl::Mutex maListenerMutex; + ::comphelper::OInterfaceContainerHelper3<css::document::XEventListener> maEventListeners; + std::unordered_multimap<css::uno::Reference< css::drawing::XShape >, css::uno::Reference< css::document::XShapeEventListener >> maShapeListeners; + SdrModel *mpDrawModel; +protected: + virtual ~SwDrawModellListener_Impl() override; + +public: + explicit SwDrawModellListener_Impl( SdrModel *pDrawModel ); + + // css::document::XEventBroadcaster + virtual void SAL_CALL addEventListener( const uno::Reference< document::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const uno::Reference< document::XEventListener >& xListener ) override; + // css::document::XShapeEventBroadcaster + virtual void SAL_CALL addShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape, const css::uno::Reference< css::document::XShapeEventListener >& xListener ) override; + virtual void SAL_CALL removeShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape, const css::uno::Reference< css::document::XShapeEventListener >& xListener ) override; + + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + void Dispose(); +}; + +} + +SwDrawModellListener_Impl::SwDrawModellListener_Impl( SdrModel *pDrawModel ) : + maEventListeners( maListenerMutex ), + mpDrawModel( pDrawModel ) +{ + StartListening( *mpDrawModel ); +} + +SwDrawModellListener_Impl::~SwDrawModellListener_Impl() +{ + Dispose(); +} + +void SAL_CALL SwDrawModellListener_Impl::addEventListener( const uno::Reference< document::XEventListener >& xListener ) +{ + maEventListeners.addInterface( xListener ); +} + +void SAL_CALL SwDrawModellListener_Impl::removeEventListener( const uno::Reference< document::XEventListener >& xListener ) +{ + maEventListeners.removeInterface( xListener ); +} + +void SAL_CALL SwDrawModellListener_Impl::addShapeEventListener( + const css::uno::Reference< css::drawing::XShape >& xShape, + const uno::Reference< document::XShapeEventListener >& xListener ) +{ + assert(xShape.is() && "no shape?"); + osl::MutexGuard aGuard(maListenerMutex); + maShapeListeners.emplace(xShape, xListener); +} + +void SAL_CALL SwDrawModellListener_Impl::removeShapeEventListener( + const css::uno::Reference< css::drawing::XShape >& xShape, + const uno::Reference< document::XShapeEventListener >& xListener ) +{ + osl::MutexGuard aGuard(maListenerMutex); + auto [itBegin, itEnd] = maShapeListeners.equal_range(xShape); + for (auto it = itBegin; it != itEnd; ++it) + if (it->second == xListener) + { + maShapeListeners.erase(it); + return; + } +} + +void SwDrawModellListener_Impl::Notify( SfxBroadcaster& /*rBC*/, + const SfxHint& rHint ) +{ + // do not broadcast notifications for writer fly frames, because there + // are no shapes that need to know about them. + if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint) + return; + const SdrHint *pSdrHint = static_cast<const SdrHint*>( &rHint ); + if (pSdrHint->GetObject() && + ( dynamic_cast< const SwFlyDrawObj* >(pSdrHint->GetObject()) != nullptr || + dynamic_cast< const SwVirtFlyDrawObj* >(pSdrHint->GetObject()) != nullptr || + isType<SdrObject>(pSdrHint->GetObject()) ) ) + { + return; + } + + OSL_ENSURE( mpDrawModel, "draw model listener is disposed" ); + if( !mpDrawModel ) + return; + + document::EventObject aEvent; + if( !SvxUnoDrawMSFactory::createEvent( mpDrawModel, pSdrHint, aEvent ) ) + return; + + ::comphelper::OInterfaceIteratorHelper3 aIter( maEventListeners ); + while( aIter.hasMoreElements() ) + { + try + { + aIter.next()->notifyEvent( aEvent ); + } + catch( uno::RuntimeException const & ) + { + TOOLS_WARN_EXCEPTION("sw.a11y", "Runtime exception caught while notifying shape"); + } + } + + // right now, we're only handling the specific event necessary to fix this performance problem + if (pSdrHint->GetKind() == SdrHintKind::ObjectChange) + { + auto pSdrObject = const_cast<SdrObject*>(pSdrHint->GetObject()); + uno::Reference<drawing::XShape> xShape(pSdrObject->getUnoShape(), uno::UNO_QUERY); + osl::MutexGuard aGuard(maListenerMutex); + auto [itBegin, itEnd] = maShapeListeners.equal_range(xShape); + for (auto it = itBegin; it != itEnd; ++it) + it->second->notifyShapeEvent(aEvent); + } +} + +void SwDrawModellListener_Impl::Dispose() +{ + if (mpDrawModel != nullptr) { + EndListening( *mpDrawModel ); + } + mpDrawModel = nullptr; +} + +typedef std::pair < const SdrObject *, ::rtl::Reference < ::accessibility::AccessibleShape > > SwAccessibleObjShape_Impl; + +class SwAccessibleShapeMap_Impl +{ +public: + + typedef const SdrObject * key_type; + typedef uno::WeakReference<XAccessible> mapped_type; + typedef std::pair<const key_type,mapped_type> value_type; + typedef std::map<key_type, mapped_type>::iterator iterator; + typedef std::map<key_type, mapped_type>::const_iterator const_iterator; + +private: + + ::accessibility::AccessibleShapeTreeInfo maInfo; + std::map<key_type, mapped_type> maMap; + +public: + + explicit SwAccessibleShapeMap_Impl( SwAccessibleMap const *pMap ) + { + maInfo.SetSdrView( pMap->GetShell()->GetDrawView() ); + maInfo.SetWindow( pMap->GetShell()->GetWin() ); + maInfo.SetViewForwarder( pMap ); + uno::Reference < document::XShapeEventBroadcaster > xModelBroadcaster = + new SwDrawModellListener_Impl( + pMap->GetShell()->getIDocumentDrawModelAccess().GetOrCreateDrawModel() ); + maInfo.SetModelBroadcaster( xModelBroadcaster ); + } + + ~SwAccessibleShapeMap_Impl(); + + const ::accessibility::AccessibleShapeTreeInfo& GetInfo() const { return maInfo; } + + std::unique_ptr<SwAccessibleObjShape_Impl[]> Copy( size_t& rSize, + const SwFEShell *pFESh, + SwAccessibleObjShape_Impl **pSelShape ) const; + + iterator end() { return maMap.end(); } + const_iterator cbegin() const { return maMap.cbegin(); } + const_iterator cend() const { return maMap.cend(); } + bool empty() const { return maMap.empty(); } + iterator find(const key_type& key) { return maMap.find(key); } + template<class... Args> + std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); } + iterator erase(const_iterator const & pos) { return maMap.erase(pos); } +}; + +SwAccessibleShapeMap_Impl::~SwAccessibleShapeMap_Impl() +{ + uno::Reference < document::XEventBroadcaster > xBrd( maInfo.GetModelBroadcaster() ); + if( xBrd.is() ) + static_cast < SwDrawModellListener_Impl * >( xBrd.get() )->Dispose(); +} + +std::unique_ptr<SwAccessibleObjShape_Impl[]> + SwAccessibleShapeMap_Impl::Copy( + size_t& rSize, const SwFEShell *pFESh, + SwAccessibleObjShape_Impl **pSelStart ) const +{ + std::unique_ptr<SwAccessibleObjShape_Impl[]> pShapes; + SwAccessibleObjShape_Impl *pSelShape = nullptr; + + size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0; + rSize = maMap.size(); + + if( rSize > 0 ) + { + pShapes.reset(new SwAccessibleObjShape_Impl[rSize]); + + SwAccessibleObjShape_Impl *pShape = pShapes.get(); + pSelShape = &(pShapes[rSize]); + for( const auto& rEntry : maMap ) + { + const SdrObject *pObj = rEntry.first; + uno::Reference < XAccessible > xAcc( rEntry.second ); + if( nSelShapes && pFESh && pFESh->IsObjSelected( *pObj ) ) + { + // selected objects are inserted from the back + --pSelShape; + pSelShape->first = pObj; + pSelShape->second = + static_cast < ::accessibility::AccessibleShape* >( + xAcc.get() ); + --nSelShapes; + } + else + { + pShape->first = pObj; + pShape->second = + static_cast < ::accessibility::AccessibleShape* >( + xAcc.get() ); + ++pShape; + } + } + assert(pSelShape == pShape); + } + + if( pSelStart ) + *pSelStart = pSelShape; + + return pShapes; +} + +struct SwAccessibleEvent_Impl +{ +public: + enum EventType { CARET_OR_STATES, + INVALID_CONTENT, + POS_CHANGED, + CHILD_POS_CHANGED, + SHAPE_SELECTION, + DISPOSE, + INVALID_ATTR }; + +private: + SwRect maOldBox; // the old bounds for CHILD_POS_CHANGED + // and POS_CHANGED + uno::WeakReference < XAccessible > mxAcc; // The object that fires the event + SwAccessibleChild maFrameOrObj; // the child for CHILD_POS_CHANGED and + // the same as xAcc for any other + // event type + EventType meType; // The event type + AccessibleStates mnStates; // check states or update caret pos + +public: + const SwFrame* mpParentFrame; // The object that fires the event + bool IsNoXaccParentFrame() const + { + return CHILD_POS_CHANGED == meType && mpParentFrame != nullptr; + } + +public: + SwAccessibleEvent_Impl( EventType eT, + SwAccessibleContext *pA, + const SwAccessibleChild& rFrameOrObj ) + : mxAcc( pA ), + maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( nullptr ) + {} + + SwAccessibleEvent_Impl( EventType eT, + const SwAccessibleChild& rFrameOrObj ) + : maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( nullptr ) + { + assert(SwAccessibleEvent_Impl::DISPOSE == meType && + "wrong event constructor, DISPOSE only"); + } + + explicit SwAccessibleEvent_Impl( EventType eT ) + : meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( nullptr ) + { + assert(SwAccessibleEvent_Impl::SHAPE_SELECTION == meType && + "wrong event constructor, SHAPE_SELECTION only" ); + } + + SwAccessibleEvent_Impl( EventType eT, + SwAccessibleContext *pA, + const SwAccessibleChild& rFrameOrObj, + const SwRect& rR ) + : maOldBox( rR ), + mxAcc( pA ), + maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( nullptr ) + { + assert((SwAccessibleEvent_Impl::CHILD_POS_CHANGED == meType || + SwAccessibleEvent_Impl::POS_CHANGED == meType) && + "wrong event constructor, (CHILD_)POS_CHANGED only" ); + } + + SwAccessibleEvent_Impl( EventType eT, + SwAccessibleContext *pA, + const SwAccessibleChild& rFrameOrObj, + const AccessibleStates _nStates ) + : mxAcc( pA ), + maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( _nStates ), + mpParentFrame( nullptr ) + { + assert( SwAccessibleEvent_Impl::CARET_OR_STATES == meType && + "wrong event constructor, CARET_OR_STATES only" ); + } + + SwAccessibleEvent_Impl( EventType eT, const SwFrame *pParentFrame, + const SwAccessibleChild& rFrameOrObj, const SwRect& rR ) : + maOldBox( rR ), + maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( pParentFrame ) + { + assert( SwAccessibleEvent_Impl::CHILD_POS_CHANGED == meType && + "wrong event constructor, CHILD_POS_CHANGED only" ); + } + + // <SetType(..)> only used in method <SwAccessibleMap::AppendEvent(..)> + void SetType( EventType eT ) + { + meType = eT; + } + EventType GetType() const + { + return meType; + } + + ::rtl::Reference < SwAccessibleContext > GetContext() const + { + uno::Reference < XAccessible > xTmp( mxAcc ); + ::rtl::Reference < SwAccessibleContext > xAccImpl( + static_cast<SwAccessibleContext*>( xTmp.get() ) ); + + return xAccImpl; + } + + const SwRect& GetOldBox() const + { + return maOldBox; + } + // <SetOldBox(..)> only used in method <SwAccessibleMap::AppendEvent(..)> + void SetOldBox( const SwRect& rOldBox ) + { + maOldBox = rOldBox; + } + + const SwAccessibleChild& GetFrameOrObj() const + { + return maFrameOrObj; + } + + // <SetStates(..)> only used in method <SwAccessibleMap::AppendEvent(..)> + void SetStates( AccessibleStates _nStates ) + { + mnStates |= _nStates; + } + + bool IsUpdateCursorPos() const + { + return bool(mnStates & AccessibleStates::CARET); + } + bool IsInvalidateStates() const + { + return bool(mnStates & (AccessibleStates::EDITABLE | AccessibleStates::OPAQUE)); + } + bool IsInvalidateRelation() const + { + return bool(mnStates & (AccessibleStates::RELATION_FROM | AccessibleStates::RELATION_TO)); + } + bool IsInvalidateTextSelection() const + { + return bool( mnStates & AccessibleStates::TEXT_SELECTION_CHANGED ); + } + + bool IsInvalidateTextAttrs() const + { + return bool( mnStates & AccessibleStates::TEXT_ATTRIBUTE_CHANGED ); + } + + AccessibleStates GetStates() const + { + return mnStates; + } + + AccessibleStates GetAllStates() const + { + return mnStates; + } +}; + +class SwAccessibleEventList_Impl +{ + std::list<SwAccessibleEvent_Impl> maEvents; + bool mbFiring; + +public: + SwAccessibleEventList_Impl() + : mbFiring( false ) + {} + + void SetFiring() + { + mbFiring = true; + } + bool IsFiring() const + { + return mbFiring; + } + + void MoveMissingXAccToEnd(); + + size_t size() const { return maEvents.size(); } + std::list<SwAccessibleEvent_Impl>::iterator begin() { return maEvents.begin(); } + std::list<SwAccessibleEvent_Impl>::iterator end() { return maEvents.end(); } + std::list<SwAccessibleEvent_Impl>::iterator insert( const std::list<SwAccessibleEvent_Impl>::iterator& aIter, + const SwAccessibleEvent_Impl& rEvent ) + { + return maEvents.insert( aIter, rEvent ); + } + std::list<SwAccessibleEvent_Impl>::iterator erase( const std::list<SwAccessibleEvent_Impl>::iterator& aPos ) + { + return maEvents.erase( aPos ); + } +}; + +// see comment in SwAccessibleMap::InvalidatePosOrSize() +// last case "else if(pParent)" for why this surprising hack exists +void SwAccessibleEventList_Impl::MoveMissingXAccToEnd() +{ + size_t nSize = size(); + if (nSize < 2 ) + { + return; + } + SwAccessibleEventList_Impl lstEvent; + for (auto li = begin(); li != end(); ) + { + if (li->IsNoXaccParentFrame()) + { + lstEvent.insert(lstEvent.end(), *li); + li = erase(li); + } + else + ++li; + } + assert(size() + lstEvent.size() == nSize); + maEvents.insert(end(),lstEvent.begin(),lstEvent.end()); + assert(size() == nSize); +} + +namespace { + +struct SwAccessibleChildFunc +{ + bool operator()( const SwAccessibleChild& r1, + const SwAccessibleChild& r2 ) const + { + const void *p1 = r1.GetSwFrame() + ? static_cast < const void * >( r1.GetSwFrame()) + : ( r1.GetDrawObject() + ? static_cast < const void * >( r1.GetDrawObject() ) + : static_cast < const void * >( r1.GetWindow() ) ); + const void *p2 = r2.GetSwFrame() + ? static_cast < const void * >( r2.GetSwFrame()) + : ( r2.GetDrawObject() + ? static_cast < const void * >( r2.GetDrawObject() ) + : static_cast < const void * >( r2.GetWindow() ) ); + return p1 < p2; + } +}; + +} + +class SwAccessibleEventMap_Impl +{ +public: + typedef SwAccessibleChild key_type; + typedef std::list<SwAccessibleEvent_Impl>::iterator mapped_type; + typedef std::pair<const key_type,mapped_type> value_type; + typedef SwAccessibleChildFunc key_compare; + typedef std::map<key_type,mapped_type,key_compare>::iterator iterator; + typedef std::map<key_type,mapped_type,key_compare>::const_iterator const_iterator; +private: + std::map <key_type,mapped_type,key_compare> maMap; +public: + iterator end() { return maMap.end(); } + iterator find(const key_type& key) { return maMap.find(key); } + template<class... Args> + std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); } + iterator erase(const_iterator const & pos) { return maMap.erase(pos); } +}; + +namespace { + +struct SwAccessibleParaSelection +{ + TextFrameIndex nStartOfSelection; + TextFrameIndex nEndOfSelection; + + SwAccessibleParaSelection(const TextFrameIndex nStartOfSelection_, + const TextFrameIndex nEndOfSelection_) + : nStartOfSelection(nStartOfSelection_) + , nEndOfSelection(nEndOfSelection_) + {} +}; + +struct SwXAccWeakRefComp +{ + bool operator()( const uno::WeakReference<XAccessible>& _rXAccWeakRef1, + const uno::WeakReference<XAccessible>& _rXAccWeakRef2 ) const + { + return _rXAccWeakRef1.get() < _rXAccWeakRef2.get(); + } +}; + +} + +class SwAccessibleSelectedParas_Impl +{ +public: + typedef uno::WeakReference < XAccessible > key_type; + typedef SwAccessibleParaSelection mapped_type; + typedef std::pair<const key_type,mapped_type> value_type; + typedef SwXAccWeakRefComp key_compare; + typedef std::map<key_type,mapped_type,key_compare>::iterator iterator; + typedef std::map<key_type,mapped_type,key_compare>::const_iterator const_iterator; +private: + std::map<key_type,mapped_type,key_compare> maMap; +public: + iterator begin() { return maMap.begin(); } + iterator end() { return maMap.end(); } + iterator find(const key_type& key) { return maMap.find(key); } + template<class... Args> + std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); } + iterator erase(const_iterator const & pos) { return maMap.erase(pos); } +}; + +// helper class that stores preview data +class SwAccPreviewData +{ + typedef std::vector<tools::Rectangle> Rectangles; + Rectangles maPreviewRects; + Rectangles maLogicRects; + + SwRect maVisArea; + Fraction maScale; + + const SwPageFrame *mpSelPage; + + /** adjust logic page rectangle to its visible part + + @param _iorLogicPgSwRect + input/output parameter - reference to the logic page rectangle, which + has to be adjusted. + + @param _rPreviewPgSwRect + input parameter - constant reference to the corresponding preview page + rectangle; needed to determine the visible part of the logic page rectangle. + + @param _rPreviewWinSize + input parameter - constant reference to the preview window size in TWIP; + needed to determine the visible part of the logic page rectangle + */ + static void AdjustLogicPgRectToVisibleArea( SwRect& _iorLogicPgSwRect, + const SwRect& _rPreviewPgSwRect, + const Size& _rPreviewWinSize ); + +public: + SwAccPreviewData(); + + void Update( const SwAccessibleMap& rAccMap, + const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ); + + void InvalidateSelection( const SwPageFrame* _pSelectedPageFrame ); + + const SwRect& GetVisArea() const { return maVisArea;} + + /** Adjust the MapMode so that the preview page appears at the + * proper position. rPoint identifies the page for which the + * MapMode should be adjusted. If bFromPreview is true, rPoint is + * a preview coordinate; else it's a document coordinate. */ + void AdjustMapMode( MapMode& rMapMode, + const Point& rPoint ) const; + + const SwPageFrame *GetSelPage() const { return mpSelPage; } + + void DisposePage(const SwPageFrame *pPageFrame ); +}; + +SwAccPreviewData::SwAccPreviewData() : + mpSelPage( nullptr ) +{ +} + +void SwAccPreviewData::Update( const SwAccessibleMap& rAccMap, + const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ) +{ + // store preview scaling, maximal preview page size and selected page + maScale = _rScale; + mpSelPage = _pSelectedPageFrame; + + // prepare loop on preview pages + maPreviewRects.clear(); + maLogicRects.clear(); + SwAccessibleChild aPage; + maVisArea.Clear(); + + // loop on preview pages to calculate <maPreviewRects>, <maLogicRects> and + // <maVisArea> + for ( auto & rpPreviewPage : _rPreviewPages ) + { + aPage = rpPreviewPage->pPage; + + // add preview page rectangle to <maPreviewRects> + tools::Rectangle aPreviewPgRect( rpPreviewPage->aPreviewWinPos, rpPreviewPage->aPageSize ); + maPreviewRects.push_back( aPreviewPgRect ); + + // add logic page rectangle to <maLogicRects> + SwRect aLogicPgSwRect( aPage.GetBox( rAccMap ) ); + tools::Rectangle aLogicPgRect( aLogicPgSwRect.SVRect() ); + maLogicRects.push_back( aLogicPgRect ); + // union visible area with visible part of logic page rectangle + if ( rpPreviewPage->bVisible ) + { + if ( !rpPreviewPage->pPage->IsEmptyPage() ) + { + AdjustLogicPgRectToVisibleArea( aLogicPgSwRect, + SwRect( aPreviewPgRect ), + _rPreviewWinSize ); + } + if ( maVisArea.IsEmpty() ) + maVisArea = aLogicPgSwRect; + else + maVisArea.Union( aLogicPgSwRect ); + } + } +} + +void SwAccPreviewData::InvalidateSelection( const SwPageFrame* _pSelectedPageFrame ) +{ + mpSelPage = _pSelectedPageFrame; + assert(mpSelPage); +} + +namespace { + +struct ContainsPredicate +{ + const Point& mrPoint; + explicit ContainsPredicate( const Point& rPoint ) : mrPoint(rPoint) {} + bool operator() ( const tools::Rectangle& rRect ) const + { + return rRect.Contains( mrPoint ); + } +}; + +} + +void SwAccPreviewData::AdjustMapMode( MapMode& rMapMode, + const Point& rPoint ) const +{ + // adjust scale + rMapMode.SetScaleX( maScale ); + rMapMode.SetScaleY( maScale ); + + // find proper rectangle + Rectangles::const_iterator aBegin = maLogicRects.begin(); + Rectangles::const_iterator aEnd = maLogicRects.end(); + Rectangles::const_iterator aFound = std::find_if( aBegin, aEnd, + ContainsPredicate( rPoint ) ); + + if( aFound != aEnd ) + { + // found! set new origin + Point aPoint = (maPreviewRects.begin() + (aFound - aBegin))->TopLeft(); + aPoint -= (maLogicRects.begin() + (aFound-aBegin))->TopLeft(); + rMapMode.SetOrigin( aPoint ); + } + // else: don't adjust MapMode +} + +void SwAccPreviewData::DisposePage(const SwPageFrame *pPageFrame ) +{ + if( mpSelPage == pPageFrame ) + mpSelPage = nullptr; +} + +// adjust logic page rectangle to its visible part +void SwAccPreviewData::AdjustLogicPgRectToVisibleArea( + SwRect& _iorLogicPgSwRect, + const SwRect& _rPreviewPgSwRect, + const Size& _rPreviewWinSize ) +{ + // determine preview window rectangle + const SwRect aPreviewWinSwRect( Point( 0, 0 ), _rPreviewWinSize ); + // calculate visible preview page rectangle + SwRect aVisPreviewPgSwRect( _rPreviewPgSwRect ); + aVisPreviewPgSwRect.Intersection( aPreviewWinSwRect ); + // adjust logic page rectangle + SwTwips nTmpDiff; + // left + nTmpDiff = aVisPreviewPgSwRect.Left() - _rPreviewPgSwRect.Left(); + _iorLogicPgSwRect.AddLeft( nTmpDiff ); + // top + nTmpDiff = aVisPreviewPgSwRect.Top() - _rPreviewPgSwRect.Top(); + _iorLogicPgSwRect.AddTop( nTmpDiff ); + // right + nTmpDiff = _rPreviewPgSwRect.Right() - aVisPreviewPgSwRect.Right(); + _iorLogicPgSwRect.AddRight( - nTmpDiff ); + // bottom + nTmpDiff = _rPreviewPgSwRect.Bottom() - aVisPreviewPgSwRect.Bottom(); + _iorLogicPgSwRect.AddBottom( - nTmpDiff ); +} + +static bool AreInSameTable( const uno::Reference< XAccessible >& rAcc, + const SwFrame *pFrame ) +{ + bool bRet = false; + + if( pFrame && pFrame->IsCellFrame() && rAcc.is() ) + { + // Is it in the same table? We check that + // by comparing the last table frame in the + // follow chain, because that's cheaper than + // searching the first one. + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( rAcc.get() ); + if( pAccImpl->GetFrame()->IsCellFrame() ) + { + const SwTabFrame *pTabFrame1 = pAccImpl->GetFrame()->FindTabFrame(); + if (pTabFrame1) + { + while (pTabFrame1->GetFollow()) + pTabFrame1 = pTabFrame1->GetFollow(); + } + + const SwTabFrame *pTabFrame2 = pFrame->FindTabFrame(); + if (pTabFrame2) + { + while (pTabFrame2->GetFollow()) + pTabFrame2 = pTabFrame2->GetFollow(); + } + + bRet = (pTabFrame1 == pTabFrame2); + } + } + + return bRet; +} + +void SwAccessibleMap::FireEvent( const SwAccessibleEvent_Impl& rEvent ) +{ + ::rtl::Reference < SwAccessibleContext > xAccImpl( rEvent.GetContext() ); + if (!xAccImpl.is() && rEvent.mpParentFrame != nullptr) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( rEvent.mpParentFrame ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if (xAcc.is()) + { + uno::Reference < XAccessibleContext > xContext(xAcc,uno::UNO_QUERY); + if (xContext.is() && xContext->getAccessibleRole() == AccessibleRole::PARAGRAPH) + { + xAccImpl = static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + } + } + if( SwAccessibleEvent_Impl::SHAPE_SELECTION == rEvent.GetType() ) + { + DoInvalidateShapeSelection(); + } + else if( xAccImpl.is() && xAccImpl->GetFrame() ) + { + if ( rEvent.GetType() != SwAccessibleEvent_Impl::DISPOSE && + rEvent.IsInvalidateTextAttrs() ) + { + xAccImpl->InvalidateAttr(); + } + switch( rEvent.GetType() ) + { + case SwAccessibleEvent_Impl::INVALID_CONTENT: + xAccImpl->InvalidateContent(); + break; + case SwAccessibleEvent_Impl::POS_CHANGED: + xAccImpl->InvalidatePosOrSize( rEvent.GetOldBox() ); + break; + case SwAccessibleEvent_Impl::CHILD_POS_CHANGED: + xAccImpl->InvalidateChildPosOrSize( rEvent.GetFrameOrObj(), + rEvent.GetOldBox() ); + break; + case SwAccessibleEvent_Impl::DISPOSE: + assert(!"dispose event has been stored"); + break; + case SwAccessibleEvent_Impl::INVALID_ATTR: + // nothing to do here - handled above + break; + default: + break; + } + if( SwAccessibleEvent_Impl::DISPOSE != rEvent.GetType() ) + { + if( rEvent.IsUpdateCursorPos() ) + xAccImpl->InvalidateCursorPos(); + if( rEvent.IsInvalidateStates() ) + xAccImpl->InvalidateStates( rEvent.GetStates() ); + if( rEvent.IsInvalidateRelation() ) + { + // both events CONTENT_FLOWS_FROM_RELATION_CHANGED and + // CONTENT_FLOWS_TO_RELATION_CHANGED are possible + if ( rEvent.GetAllStates() & AccessibleStates::RELATION_FROM ) + { + xAccImpl->InvalidateRelation( + AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED ); + } + if ( rEvent.GetAllStates() & AccessibleStates::RELATION_TO ) + { + xAccImpl->InvalidateRelation( + AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED ); + } + } + + if ( rEvent.IsInvalidateTextSelection() ) + { + xAccImpl->InvalidateTextSelection(); + } + } + } +} + +void SwAccessibleMap::AppendEvent( const SwAccessibleEvent_Impl& rEvent ) +{ + osl::MutexGuard aGuard( maEventMutex ); + + if( !mpEvents ) + mpEvents.reset(new SwAccessibleEventList_Impl); + if( !mpEventMap ) + mpEventMap.reset(new SwAccessibleEventMap_Impl); + + if( mpEvents->IsFiring() ) + { + // While events are fired new ones are generated. They have to be fired + // now. This does not work for DISPOSE events! + OSL_ENSURE( rEvent.GetType() != SwAccessibleEvent_Impl::DISPOSE, + "dispose event while firing events" ); + FireEvent( rEvent ); + } + else + { + + SwAccessibleEventMap_Impl::iterator aIter = + mpEventMap->find( rEvent.GetFrameOrObj() ); + if( aIter != mpEventMap->end() ) + { + SwAccessibleEvent_Impl aEvent( *(*aIter).second ); + assert( aEvent.GetType() != SwAccessibleEvent_Impl::DISPOSE && + "dispose events should not be stored" ); + bool bAppendEvent = true; + switch( rEvent.GetType() ) + { + case SwAccessibleEvent_Impl::CARET_OR_STATES: + // A CARET_OR_STATES event is added to any other + // event only. It is broadcasted after any other event, so the + // event should be put to the back. + OSL_ENSURE( aEvent.GetType() != SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + "invalid event combination" ); + aEvent.SetStates( rEvent.GetAllStates() ); + break; + case SwAccessibleEvent_Impl::INVALID_CONTENT: + // An INVALID_CONTENT event overwrites a CARET_OR_STATES + // event (but keeps its flags) and it is contained in a + // POS_CHANGED event. + // Therefore, the event's type has to be adapted and the event + // has to be put at the end. + // + // fdo#56031 An INVALID_CONTENT event overwrites a INVALID_ATTR + // event and overwrites its flags + OSL_ENSURE( aEvent.GetType() != SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + "invalid event combination" ); + if( aEvent.GetType() == SwAccessibleEvent_Impl::CARET_OR_STATES ) + aEvent.SetType( SwAccessibleEvent_Impl::INVALID_CONTENT ); + else if ( aEvent.GetType() == SwAccessibleEvent_Impl::INVALID_ATTR ) + { + aEvent.SetType( SwAccessibleEvent_Impl::INVALID_CONTENT ); + aEvent.SetStates( rEvent.GetAllStates() ); + } + + break; + case SwAccessibleEvent_Impl::POS_CHANGED: + // A pos changed event overwrites CARET_STATES (keeping its + // flags) as well as INVALID_CONTENT. The old box position + // has to be stored however if the old event is not a + // POS_CHANGED itself. + OSL_ENSURE( aEvent.GetType() != SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + "invalid event combination" ); + if( aEvent.GetType() != SwAccessibleEvent_Impl::POS_CHANGED ) + aEvent.SetOldBox( rEvent.GetOldBox() ); + aEvent.SetType( SwAccessibleEvent_Impl::POS_CHANGED ); + break; + case SwAccessibleEvent_Impl::CHILD_POS_CHANGED: + // CHILD_POS_CHANGED events can only follow CHILD_POS_CHANGED + // events. The only action that needs to be done again is + // to put the old event to the back. The new one cannot be used, + // because we are interested in the old frame bounds. + OSL_ENSURE( aEvent.GetType() == SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + "invalid event combination" ); + break; + case SwAccessibleEvent_Impl::SHAPE_SELECTION: + OSL_ENSURE( aEvent.GetType() == SwAccessibleEvent_Impl::SHAPE_SELECTION, + "invalid event combination" ); + break; + case SwAccessibleEvent_Impl::DISPOSE: + // DISPOSE events overwrite all others. They are not stored + // but executed immediately to avoid broadcasting of + // nonfunctional objects. So what needs to be done here is to + // remove all events for the frame in question. + bAppendEvent = false; + break; + case SwAccessibleEvent_Impl::INVALID_ATTR: + OSL_ENSURE( aEvent.GetType() == SwAccessibleEvent_Impl::INVALID_ATTR, + "invalid event combination" ); + break; + } + if( bAppendEvent ) + { + mpEvents->erase( (*aIter).second ); + (*aIter).second = mpEvents->insert( mpEvents->end(), aEvent ); + } + else + { + mpEvents->erase( (*aIter).second ); + mpEventMap->erase( aIter ); + } + } + else if( SwAccessibleEvent_Impl::DISPOSE != rEvent.GetType() ) + { + mpEventMap->emplace( rEvent.GetFrameOrObj(), + mpEvents->insert( mpEvents->end(), rEvent ) ); + } + } +} + +void SwAccessibleMap::InvalidateCursorPosition( + const uno::Reference< XAccessible >& rAcc ) +{ + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( rAcc.get() ); + assert(pAccImpl); + assert(pAccImpl->GetFrame()); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::CARET_OR_STATES, + pAccImpl, + SwAccessibleChild(pAccImpl->GetFrame()), + AccessibleStates::CARET ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + // While firing events the current frame might have + // been disposed because it moved out of the visible area. + // Setting the cursor for such frames is useless and even + // causes asserts. + if( pAccImpl->GetFrame() ) + pAccImpl->InvalidateCursorPos(); + } +} + +void SwAccessibleMap::InvalidateShapeSelection() +{ + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::SHAPE_SELECTION ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + DoInvalidateShapeSelection(); + } +} + +//This method should implement the following functions: +//1.find the shape objects and set the selected state. +//2.find the Swframe objects and set the selected state. +//3.find the paragraph objects and set the selected state. +void SwAccessibleMap::InvalidateShapeInParaSelection() +{ + std::unique_ptr<SwAccessibleObjShape_Impl[]> pShapes; + SwAccessibleObjShape_Impl *pSelShape = nullptr; + size_t nShapes = 0; + + const SwViewShell *pVSh = GetShell(); + const SwFEShell *pFESh = dynamic_cast<const SwFEShell*>(pVSh); + SwPaM* pCursor = pFESh ? pFESh->GetCursor( false /* ??? */ ) : nullptr; + + //const size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0; + + { + osl::MutexGuard aGuard( maMutex ); + if( mpShapeMap ) + pShapes = mpShapeMap->Copy( nShapes, pFESh, &pSelShape ); + } + + bool bIsSelAll =IsDocumentSelAll(); + + if( mpShapeMap ) + { + //Checked for shapes. + SwAccessibleShapeMap_Impl::const_iterator aIter = mpShapeMap->cbegin(); + SwAccessibleShapeMap_Impl::const_iterator aEndIter = mpShapeMap->cend(); + + if( bIsSelAll) + { + while( aIter != aEndIter ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if( xAcc.is() ) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->SetState( AccessibleStateType::SELECTED ); + + ++aIter; + } + } + else + { + while( aIter != aEndIter ) + { + const SwFrameFormat *pFrameFormat = (*aIter).first ? ::FindFrameFormat( (*aIter).first ) : nullptr; + if( !pFrameFormat ) + { + ++aIter; + continue; + } + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + const SwPosition *pPos = rAnchor.GetContentAnchor(); + + if(rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if(xAcc.is()) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + + ++aIter; + continue; + } + + if( !pPos ) + { + ++aIter; + continue; + } + if( pPos->nNode.GetNode().GetTextNode() ) + { + int nIndex = pPos->nContent.GetIndex(); + bool bMarked = false; + if( pCursor != nullptr ) + { + const SwTextNode* pNode = pPos->nNode.GetNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(pNode->getLayoutFrame(pVSh->GetLayout()))); + SwNodeOffset nFirstNode(pFrame->GetTextNodeFirst()->GetIndex()); + SwNodeOffset nLastNode; + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + nLastNode = pMerged->pLastNode->GetIndex(); + } + else + { + nLastNode = nFirstNode; + } + + SwNodeOffset nHere = pNode->GetIndex(); + + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + // ignore, if no mark + if( rTmpCursor.HasMark() ) + { + bMarked = true; + // check whether nHere is 'inside' pCursor + SwPosition* pStart = rTmpCursor.Start(); + SwNodeOffset nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = rTmpCursor.End(); + SwNodeOffset nEndIndex = pEnd->nNode.GetIndex(); + if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex)) + { + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + if( ( ((nHere == nStartIndex) && (nIndex >= pStart->nContent.GetIndex())) || (nHere > nStartIndex) ) + &&( ((nHere == nEndIndex) && (nIndex < pEnd->nContent.GetIndex())) || (nHere < nEndIndex) ) ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if( xAcc.is() ) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->SetState( AccessibleStateType::SELECTED ); + } + else + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if( xAcc.is() ) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + } + } + else if( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA ) + { + uno::Reference<XAccessible> const xAcc((*aIter).second); + if (xAcc.is()) + { + if (IsSelectFrameAnchoredAtPara(*pPos, *pStart, *pEnd)) + { + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->SetState( AccessibleStateType::SELECTED ); + } + else + { + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + } + } + } + else if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + uno::Reference<XAccessible> const xAcc((*aIter).second); + if (xAcc.is()) + { + if (IsDestroyFrameAnchoredAtChar(*pPos, *pStart, *pEnd)) + { + static_cast<::accessibility::AccessibleShape*>(xAcc.get())->SetState( AccessibleStateType::SELECTED ); + } + else + { + static_cast<::accessibility::AccessibleShape*>(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + } + } + } + } + } + } + } + if( !bMarked ) + { + SwAccessibleObjShape_Impl *pShape = pShapes.get(); + size_t nNumShapes = nShapes; + while( nNumShapes ) + { + if( pShape < pSelShape && (pShape->first==(*aIter).first) ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if(xAcc.is()) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + } + --nNumShapes; + ++pShape; + } + } + } + + ++aIter; + }//while( aIter != aEndIter ) + }//else + } + + pShapes.reset(); + + //Checked for FlyFrame + if (mpFrameMap) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->begin(); + while( aIter != mpFrameMap->end() ) + { + const SwFrame *pFrame = (*aIter).first; + if(pFrame->IsFlyFrame()) + { + uno::Reference < XAccessible > xAcc = (*aIter).second; + + if(xAcc.is()) + { + SwAccessibleFrameBase *pAccFrame = static_cast< SwAccessibleFrameBase * >(xAcc.get()); + bool bFrameChanged = pAccFrame->SetSelectedState( true ); + if (bFrameChanged) + { + const SwFlyFrame *pFlyFrame = static_cast< const SwFlyFrame * >( pFrame ); + const SwFrameFormat *pFrameFormat = pFlyFrame->GetFormat(); + if (pFrameFormat) + { + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + uno::Reference< XAccessible > xAccParent = pAccFrame->getAccessibleParent(); + if (xAccParent.is()) + { + uno::Reference< XAccessibleContext > xAccContext = xAccParent->getAccessibleContext(); + if(xAccContext.is() && xAccContext->getAccessibleRole() == AccessibleRole::PARAGRAPH) + { + SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xAccContext.get()); + if(pAccFrame->IsSelectedInDoc()) + { + m_setParaAdd.insert(pAccPara); + } + else if(m_setParaAdd.count(pAccPara) == 0) + { + m_setParaRemove.insert(pAccPara); + } + } + } + } + } + } + } + } + ++aIter; + } + } + + typedef std::vector< SwAccessibleContext* > VEC_PARA; + VEC_PARA vecAdd; + VEC_PARA vecRemove; + //Checked for Paras. + bool bMarkChanged = false; + SwAccessibleContextMap_Impl mapTemp; + if( pCursor != nullptr ) + { + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + if( rTmpCursor.HasMark() ) + { + SwNodeIndex nStartIndex( rTmpCursor.Start()->nNode ); + SwNodeIndex nEndIndex( rTmpCursor.End()->nNode ); + for (; nStartIndex <= nEndIndex; ++nStartIndex) + { + SwFrame *pFrame = nullptr; + if(nStartIndex.GetNode().IsContentNode()) + { + SwContentNode* pCNd = static_cast<SwContentNode*>(&(nStartIndex.GetNode())); + pFrame = SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*pCNd).First(); + if (mapTemp.find(pFrame) != mapTemp.end()) + { + continue; // sw_redlinehide: once is enough + } + } + else if( nStartIndex.GetNode().IsTableNode() ) + { + SwTableNode * pTable = static_cast<SwTableNode *>(&(nStartIndex.GetNode())); + SwTableFormat* pFormat = pTable->GetTable().GetFrameFormat(); + pFrame = SwIterator<SwFrame, SwTableFormat>(*pFormat).First(); + } + + if( pFrame && mpFrameMap) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pFrame ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc = (*aIter).second; + bool isChanged = false; + if( xAcc.is() ) + { + isChanged = static_cast< SwAccessibleContext * >(xAcc.get())->SetSelectedState( true ); + } + if(!isChanged) + { + SwAccessibleContextMap_Impl::iterator aEraseIter = mpSelectedFrameMap->find( pFrame ); + if(aEraseIter != mpSelectedFrameMap->end()) + mpSelectedFrameMap->erase(aEraseIter); + } + else + { + bMarkChanged = true; + vecAdd.push_back(static_cast< SwAccessibleContext * >(xAcc.get())); + } + + mapTemp.emplace( pFrame, xAcc ); + } + } + } + } + } + } + if( !mpSelectedFrameMap ) + mpSelectedFrameMap.reset( new SwAccessibleContextMap_Impl ); + if( !mpSelectedFrameMap->empty() ) + { + SwAccessibleContextMap_Impl::iterator aIter = mpSelectedFrameMap->begin(); + while( aIter != mpSelectedFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc = (*aIter).second; + if(xAcc.is()) + static_cast< SwAccessibleContext * >(xAcc.get())->SetSelectedState( false ); + ++aIter; + vecRemove.push_back(static_cast< SwAccessibleContext * >(xAcc.get())); + } + bMarkChanged = true; + mpSelectedFrameMap->clear(); + } + + SwAccessibleContextMap_Impl::iterator aIter = mapTemp.begin(); + while( aIter != mapTemp.end() ) + { + mpSelectedFrameMap->emplace( (*aIter).first, (*aIter).second ); + ++aIter; + } + mapTemp.clear(); + + if( !(bMarkChanged && mpFrameMap)) + return; + + for (SwAccessibleContext* pAccPara : vecAdd) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + if (pAccPara) + { + pAccPara->FireAccessibleEvent( aEvent ); + } + } + for (SwAccessibleContext* pAccPara : vecRemove) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE; + if (pAccPara) + { + pAccPara->FireAccessibleEvent( aEvent ); + } + } +} + +//Merge with DoInvalidateShapeFocus +void SwAccessibleMap::DoInvalidateShapeSelection(bool bInvalidateFocusMode /*=false*/) +{ + std::unique_ptr<SwAccessibleObjShape_Impl[]> pShapes; + SwAccessibleObjShape_Impl *pSelShape = nullptr; + size_t nShapes = 0; + + const SwViewShell *pVSh = GetShell(); + const SwFEShell *pFESh = dynamic_cast<const SwFEShell*>(pVSh); + const size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0; + + //when InvalidateFocus Call this function ,and the current selected shape count is not 1 , + //return + if (bInvalidateFocusMode && nSelShapes != 1) + { + return; + } + { + osl::MutexGuard aGuard( maMutex ); + if( mpShapeMap ) + pShapes = mpShapeMap->Copy( nShapes, pFESh, &pSelShape ); + } + + if( !pShapes ) + return; + + typedef std::vector< ::rtl::Reference < ::accessibility::AccessibleShape > > VEC_SHAPE; + VEC_SHAPE vecxShapeAdd; + VEC_SHAPE vecxShapeRemove; + int nCountSelectedShape=0; + + vcl::Window *pWin = GetShell()->GetWin(); + bool bFocused = pWin && pWin->HasFocus(); + SwAccessibleObjShape_Impl *pShape = pShapes.get(); + int nShapeCount = nShapes; + while( nShapeCount ) + { + if (pShape->second.is() && IsInSameLevel(pShape->first, pFESh)) + { + if( pShape < pSelShape ) + { + if(pShape->second->ResetState( AccessibleStateType::SELECTED )) + { + vecxShapeRemove.push_back(pShape->second); + } + pShape->second->ResetState( AccessibleStateType::FOCUSED ); + } + } + --nShapeCount; + ++pShape; + } + + for (const auto& rpShape : vecxShapeRemove) + { + ::accessibility::AccessibleShape *pAccShape = rpShape.get(); + if (pAccShape) + { + pAccShape->CommitChange(AccessibleEventId::SELECTION_CHANGED_REMOVE, uno::Any(), uno::Any()); + } + } + + pShape = pShapes.get(); + + while( nShapes ) + { + if (pShape->second.is() && IsInSameLevel(pShape->first, pFESh)) + { + if( pShape >= pSelShape ) + { + //first fire focus event + if( bFocused && 1 == nSelShapes ) + pShape->second->SetState( AccessibleStateType::FOCUSED ); + else + pShape->second->ResetState( AccessibleStateType::FOCUSED ); + + if(pShape->second->SetState( AccessibleStateType::SELECTED )) + { + vecxShapeAdd.push_back(pShape->second); + } + ++nCountSelectedShape; + } + } + + --nShapes; + ++pShape; + } + + const unsigned int SELECTION_WITH_NUM = 10; + if (vecxShapeAdd.size() > SELECTION_WITH_NUM ) + { + uno::Reference< XAccessible > xDoc = GetDocumentView( ); + SwAccessibleContext * pCont = static_cast<SwAccessibleContext *>(xDoc.get()); + if (pCont) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN; + pCont->FireAccessibleEvent(aEvent); + } + } + else + { + short nEventID = AccessibleEventId::SELECTION_CHANGED_ADD; + if (nCountSelectedShape <= 1 && vecxShapeAdd.size() == 1 ) + { + nEventID = AccessibleEventId::SELECTION_CHANGED; + } + for (const auto& rpShape : vecxShapeAdd) + { + ::accessibility::AccessibleShape *pAccShape = rpShape.get(); + if (pAccShape) + { + pAccShape->CommitChange(nEventID, uno::Any(), uno::Any()); + } + } + } + + for (const auto& rpShape : vecxShapeAdd) + { + ::accessibility::AccessibleShape *pAccShape = rpShape.get(); + if (pAccShape) + { + SdrObject *pObj = SdrObject::getSdrObjectFromXShape(pAccShape->GetXShape()); + SwFrameFormat *pFrameFormat = pObj ? FindFrameFormat( pObj ) : nullptr; + if (pFrameFormat) + { + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + uno::Reference< XAccessible > xPara = pAccShape->getAccessibleParent(); + if (xPara.is()) + { + uno::Reference< XAccessibleContext > xParaContext = xPara->getAccessibleContext(); + if (xParaContext.is() && xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH) + { + SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xPara.get()); + if (pAccPara) + { + m_setParaAdd.insert(pAccPara); + } + } + } + } + } + } + } + for (const auto& rpShape : vecxShapeRemove) + { + ::accessibility::AccessibleShape *pAccShape = rpShape.get(); + if (pAccShape && !pAccShape->IsDisposed()) + { + uno::Reference< XAccessible > xPara = pAccShape->getAccessibleParent(); + uno::Reference< XAccessibleContext > xParaContext = xPara->getAccessibleContext(); + if (xParaContext.is() && xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH) + { + SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xPara.get()); + if (m_setParaAdd.count(pAccPara) == 0 ) + { + m_setParaRemove.insert(pAccPara); + } + } + } + } +} + +SwAccessibleMap::SwAccessibleMap( SwViewShell *pSh ) : + mpVSh( pSh ), + mbShapeSelected( false ), + maDocName(SwAccessibleContext::GetResource(STR_ACCESS_DOC_NAME)) +{ + pSh->GetLayout()->AddAccessibleShell(); +} + +SwAccessibleMap::~SwAccessibleMap() +{ + DBG_TESTSOLARMUTEX(); + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + if( mpFrameMap ) + { + const SwRootFrame *pRootFrame = GetShell()->GetLayout(); + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pRootFrame ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + if( !xAcc.is() ) + assert(false); // let's hope this can't happen? the vcl::Window apparently owns the top-level + //xAcc = new SwAccessibleDocument(shared_from_this()); + } + } + + if(xAcc.is()) + { + SwAccessibleDocumentBase *const pAcc = + static_cast<SwAccessibleDocumentBase *>(xAcc.get()); + pAcc->Dispose( true ); + } +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->begin(); + while( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xTmp = (*aIter).second; + if( xTmp.is() ) + { + SwAccessibleContext *pTmp = static_cast< SwAccessibleContext * >( xTmp.get() ); + assert(pTmp->GetMap() == nullptr); // must be disposed + } + ++aIter; + } + } +#endif + { + osl::MutexGuard aGuard( maMutex ); + assert((!mpFrameMap || mpFrameMap->empty()) && + "Frame map should be empty after disposing the root frame"); + assert((!mpShapeMap || mpShapeMap->empty()) && + "Object map should be empty after disposing the root frame"); + mpFrameMap.reset(); + mpShapeMap.reset(); + mvShapes.clear(); + mpSelectedParas.reset(); + } + + mpPreview.reset(); + + { + osl::MutexGuard aGuard( maEventMutex ); + assert(!mpEvents); + assert(!mpEventMap); + mpEventMap.reset(); + mpEvents.reset(); + } + mpVSh->GetLayout()->RemoveAccessibleShell(); +} + +uno::Reference< XAccessible > SwAccessibleMap::GetDocumentView_( + bool bPagePreview ) +{ + uno::Reference < XAccessible > xAcc; + bool bSetVisArea = false; + + { + osl::MutexGuard aGuard( maMutex ); + + if( !mpFrameMap ) + { + mpFrameMap.reset(new SwAccessibleContextMap_Impl); +#if OSL_DEBUG_LEVEL > 0 + mpFrameMap->mbLocked = false; +#endif + } + +#if OSL_DEBUG_LEVEL > 0 + assert(!mpFrameMap->mbLocked); + mpFrameMap->mbLocked = true; +#endif + + const SwRootFrame *pRootFrame = GetShell()->GetLayout(); + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pRootFrame ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + if( xAcc.is() ) + { + bSetVisArea = true; // Set VisArea when map mutex is not locked + } + else + { + if( bPagePreview ) + xAcc = new SwAccessiblePreview(shared_from_this()); + else + xAcc = new SwAccessibleDocument(shared_from_this()); + + if( aIter != mpFrameMap->end() ) + { + (*aIter).second = xAcc; + } + else + { + mpFrameMap->emplace( pRootFrame, xAcc ); + } + } + +#if OSL_DEBUG_LEVEL > 0 + mpFrameMap->mbLocked = false; +#endif + } + + if( bSetVisArea ) + { + SwAccessibleDocumentBase *pAcc = + static_cast< SwAccessibleDocumentBase * >( xAcc.get() ); + pAcc->SetVisArea(); + } + + return xAcc; +} + +uno::Reference< XAccessible > SwAccessibleMap::GetDocumentView( ) +{ + return GetDocumentView_( false ); +} + +uno::Reference<XAccessible> SwAccessibleMap::GetDocumentPreview( + const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ) +{ + // create & update preview data object + if( mpPreview == nullptr ) + mpPreview.reset( new SwAccPreviewData() ); + mpPreview->Update( *this, _rPreviewPages, _rScale, _pSelectedPageFrame, _rPreviewWinSize ); + + uno::Reference<XAccessible> xAcc = GetDocumentView_( true ); + return xAcc; +} + +uno::Reference< XAccessible> SwAccessibleMap::GetContext( const SwFrame *pFrame, + bool bCreate ) +{ + DBG_TESTSOLARMUTEX(); + uno::Reference < XAccessible > xAcc; + uno::Reference < XAccessible > xOldCursorAcc; + bool bOldShapeSelected = false; + + { + osl::MutexGuard aGuard( maMutex ); + + if( !mpFrameMap && bCreate ) + mpFrameMap.reset(new SwAccessibleContextMap_Impl); + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pFrame ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + + if( !xAcc.is() && bCreate ) + { + rtl::Reference<SwAccessibleContext> pAcc; + switch( pFrame->GetType() ) + { + case SwFrameType::Txt: + pAcc = new SwAccessibleParagraph(shared_from_this(), + static_cast< const SwTextFrame& >( *pFrame ) ); + break; + case SwFrameType::Header: + pAcc = new SwAccessibleHeaderFooter(shared_from_this(), + static_cast< const SwHeaderFrame *>( pFrame ) ); + break; + case SwFrameType::Footer: + pAcc = new SwAccessibleHeaderFooter(shared_from_this(), + static_cast< const SwFooterFrame *>( pFrame ) ); + break; + case SwFrameType::Ftn: + { + const SwFootnoteFrame *pFootnoteFrame = + static_cast < const SwFootnoteFrame * >( pFrame ); + bool bIsEndnote = + SwAccessibleFootnote::IsEndnote( pFootnoteFrame ); + pAcc = new SwAccessibleFootnote(shared_from_this(), bIsEndnote, + /*(bIsEndnote ? mnEndnote++ : mnFootnote++),*/ + pFootnoteFrame ); + } + break; + case SwFrameType::Fly: + { + const SwFlyFrame *pFlyFrame = + static_cast < const SwFlyFrame * >( pFrame ); + switch( SwAccessibleFrameBase::GetNodeType( pFlyFrame ) ) + { + case SwNodeType::Grf: + pAcc = new SwAccessibleGraphic(shared_from_this(), pFlyFrame ); + break; + case SwNodeType::Ole: + pAcc = new SwAccessibleEmbeddedObject(shared_from_this(), pFlyFrame ); + break; + default: + pAcc = new SwAccessibleTextFrame(shared_from_this(), *pFlyFrame ); + break; + } + } + break; + case SwFrameType::Cell: + pAcc = new SwAccessibleCell(shared_from_this(), + static_cast< const SwCellFrame *>( pFrame ) ); + break; + case SwFrameType::Tab: + pAcc = new SwAccessibleTable(shared_from_this(), + static_cast< const SwTabFrame *>( pFrame ) ); + break; + case SwFrameType::Page: + OSL_ENSURE( GetShell()->IsPreview(), + "accessible page frames only in PagePreview" ); + pAcc = new SwAccessiblePage(shared_from_this(), pFrame); + break; + default: break; + } + xAcc = pAcc; + assert(xAcc.is()); + + if( aIter != mpFrameMap->end() ) + { + (*aIter).second = xAcc; + } + else + { + mpFrameMap->emplace( pFrame, xAcc ); + } + + if( pAcc->HasCursor() && + !AreInSameTable( mxCursorContext, pFrame ) ) + { + // If the new context has the focus, and if we know + // another context that had the focus, then the focus + // just moves from the old context to the new one. We + // then have to send a focus event and a caret event for + // the old context. We have to do that now, + // because after we have left this method, anyone might + // call getStates for the new context and will get a + // focused state then. Sending the focus changes event + // after that seems to be strange. However, we cannot + // send a focus event for the new context now, because + // no one except us knows it. In any case, we remember + // the new context as the one that has the focus + // currently. + + xOldCursorAcc = mxCursorContext; + mxCursorContext = xAcc; + + bOldShapeSelected = mbShapeSelected; + mbShapeSelected = false; + } + } + } + } + + // Invalidate focus for old object when map is not locked + if( xOldCursorAcc.is() ) + InvalidateCursorPosition( xOldCursorAcc ); + if( bOldShapeSelected ) + InvalidateShapeSelection(); + + return xAcc; +} + +::rtl::Reference < SwAccessibleContext > SwAccessibleMap::GetContextImpl( + const SwFrame *pFrame, + bool bCreate ) +{ + uno::Reference < XAccessible > xAcc( GetContext( pFrame, bCreate ) ); + + ::rtl::Reference < SwAccessibleContext > xAccImpl( + static_cast< SwAccessibleContext * >( xAcc.get() ) ); + + return xAccImpl; +} + +uno::Reference< XAccessible> SwAccessibleMap::GetContext( + const SdrObject *pObj, + SwAccessibleContext *pParentImpl, + bool bCreate ) +{ + uno::Reference < XAccessible > xAcc; + uno::Reference < XAccessible > xOldCursorAcc; + + { + osl::MutexGuard aGuard( maMutex ); + + if( !mpShapeMap && bCreate ) + mpShapeMap.reset(new SwAccessibleShapeMap_Impl( this )); + if( mpShapeMap ) + { + SwAccessibleShapeMap_Impl::iterator aIter = mpShapeMap->find( pObj ); + if( aIter != mpShapeMap->end() ) + xAcc = (*aIter).second; + + if( !xAcc.is() && bCreate ) + { + rtl::Reference< ::accessibility::AccessibleShape> pAcc; + uno::Reference < drawing::XShape > xShape( + const_cast< SdrObject * >( pObj )->getUnoShape(), + uno::UNO_QUERY ); + if( xShape.is() ) + { + ::accessibility::ShapeTypeHandler& rShapeTypeHandler = + ::accessibility::ShapeTypeHandler::Instance(); + uno::Reference < XAccessible > xParent( pParentImpl ); + ::accessibility::AccessibleShapeInfo aShapeInfo( + xShape, xParent, this ); + + pAcc = rShapeTypeHandler.CreateAccessibleObject( + aShapeInfo, mpShapeMap->GetInfo() ); + } + xAcc = pAcc.get(); + assert(xAcc.is()); + pAcc->Init(); + if( aIter != mpShapeMap->end() ) + { + (*aIter).second = xAcc; + } + else + { + mpShapeMap->emplace( pObj, xAcc ); + } + // TODO: focus!!! + AddGroupContext(pObj, xAcc); + } + } + } + + // Invalidate focus for old object when map is not locked + if( xOldCursorAcc.is() ) + InvalidateCursorPosition( xOldCursorAcc ); + + return xAcc; +} + +bool SwAccessibleMap::IsInSameLevel(const SdrObject* pObj, const SwFEShell* pFESh) +{ + if (pFESh) + return pFESh->IsObjSameLevelWithMarked(pObj); + return false; +} + +void SwAccessibleMap::AddShapeContext(const SdrObject *pObj, uno::Reference < XAccessible > const & xAccShape) +{ + osl::MutexGuard aGuard( maMutex ); + + if( mpShapeMap ) + { + mpShapeMap->emplace( pObj, xAccShape ); + } + +} + +//Added by yanjun for sym2_6407 +void SwAccessibleMap::RemoveGroupContext(const SdrObject *pParentObj) +{ + osl::MutexGuard aGuard( maMutex ); + // TODO: Why are sub-shapes of group shapes even added to our map? + // Doesn't the AccessibleShape of the top-level shape create them + // on demand anyway? Why does SwAccessibleMap need to know them? + // We cannot rely on getAccessibleChild here to remove the sub-shapes + // from mpShapes because the top-level shape may not only be disposed here + // but also by visibility checks in svx, then it doesn't return children. + if (mpShapeMap && pParentObj && pParentObj->IsGroupObject()) + { + SdrObjList *const pChildren(pParentObj->GetSubList()); + for (size_t i = 0; pChildren && i < pChildren->GetObjCount(); ++i) + { + SdrObject *const pChild(pChildren->GetObj(i)); + assert(pChild); + RemoveContext(pChild); + } + } +} +//End + +void SwAccessibleMap::AddGroupContext(const SdrObject *pParentObj, uno::Reference < XAccessible > const & xAccParent) +{ + osl::MutexGuard aGuard( maMutex ); + if( !mpShapeMap ) + return; + + //here get all the sub list. + if (!pParentObj->IsGroupObject()) + return; + + if (!xAccParent.is()) + return; + + uno::Reference < XAccessibleContext > xContext = xAccParent->getAccessibleContext(); + if (!xContext.is()) + return; + + sal_Int32 nChildren = xContext->getAccessibleChildCount(); + for(sal_Int32 i = 0; i<nChildren; i++) + { + uno::Reference < XAccessible > xChild = xContext->getAccessibleChild(i); + if (xChild.is()) + { + uno::Reference < XAccessibleContext > xChildContext = xChild->getAccessibleContext(); + if (xChildContext.is()) + { + short nRole = xChildContext->getAccessibleRole(); + if (nRole == AccessibleRole::SHAPE) + { + ::accessibility::AccessibleShape* pAccShape = static_cast < ::accessibility::AccessibleShape* >( xChild.get()); + uno::Reference < drawing::XShape > xShape = pAccShape->GetXShape(); + if (xShape.is()) + { + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(xShape); + AddShapeContext(pObj, xChild); + AddGroupContext(pObj,xChild); + } + } + } + } + } +} + +::rtl::Reference < ::accessibility::AccessibleShape > SwAccessibleMap::GetContextImpl( + const SdrObject *pObj, + SwAccessibleContext *pParentImpl, + bool bCreate ) +{ + uno::Reference < XAccessible > xAcc( GetContext( pObj, pParentImpl, bCreate ) ); + + ::rtl::Reference < ::accessibility::AccessibleShape > xAccImpl( + static_cast< ::accessibility::AccessibleShape* >( xAcc.get() ) ); + + return xAccImpl; +} + +void SwAccessibleMap::RemoveContext( const SwFrame *pFrame ) +{ + osl::MutexGuard aGuard( maMutex ); + + if( !mpFrameMap ) + return; + + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( pFrame ); + if( aIter == mpFrameMap->end() ) + return; + + mpFrameMap->erase( aIter ); + + // Remove reference to old caret object. Though mxCursorContext + // is a weak reference and cleared automatically, clearing it + // directly makes sure to not keep a non-functional object. + uno::Reference < XAccessible > xOldAcc( mxCursorContext ); + if( xOldAcc.is() ) + { + SwAccessibleContext *pOldAccImpl = + static_cast< SwAccessibleContext *>( xOldAcc.get() ); + OSL_ENSURE( pOldAccImpl->GetFrame(), "old caret context is disposed" ); + if( pOldAccImpl->GetFrame() == pFrame ) + { + xOldAcc.clear(); // get an empty ref + mxCursorContext = xOldAcc; + } + } + + if( mpFrameMap->empty() ) + { + mpFrameMap.reset(); + } +} + +void SwAccessibleMap::RemoveContext( const SdrObject *pObj ) +{ + osl::MutexGuard aGuard( maMutex ); + + if( !mpShapeMap ) + return; + + SwAccessibleShapeMap_Impl::iterator aIter = mpShapeMap->find( pObj ); + if( aIter == mpShapeMap->end() ) + return; + + uno::Reference < XAccessible > xTempHold( (*aIter).second ); + mpShapeMap->erase( aIter ); + RemoveGroupContext(pObj); + // The shape selection flag is not cleared, but one might do + // so but has to make sure that the removed context is the one + // that is selected. + + if( mpShapeMap && mpShapeMap->empty() ) + { + mpShapeMap.reset(); + } +} + +bool SwAccessibleMap::Contains(const SwFrame *pFrame) const +{ + return (pFrame && mpFrameMap && mpFrameMap->find(pFrame) != mpFrameMap->end()); +} + +void SwAccessibleMap::A11yDispose( const SwFrame *pFrame, + const SdrObject *pObj, + vcl::Window* pWindow, + bool bRecursive, + bool bCanSkipInvisible ) +{ + SwAccessibleChild aFrameOrObj( pFrame, pObj, pWindow ); + + // Indeed, the following assert checks the frame's accessible flag, + // because that's the one that is evaluated in the layout. The frame + // might not be accessible anyway. That's the case for cell frames that + // contain further cells. + OSL_ENSURE( !aFrameOrObj.GetSwFrame() || aFrameOrObj.GetSwFrame()->IsAccessibleFrame(), + "non accessible frame should be disposed" ); + + if (!(aFrameOrObj.IsAccessible(GetShell()->IsPreview()) + // fdo#87199 dispose the darn thing if it ever was accessible + || Contains(pFrame))) + return; + + ::rtl::Reference< SwAccessibleContext > xAccImpl; + ::rtl::Reference< SwAccessibleContext > xParentAccImpl; + ::rtl::Reference< ::accessibility::AccessibleShape > xShapeAccImpl; + // get accessible context for frame + { + osl::MutexGuard aGuard( maMutex ); + + // First of all look for an accessible context for a frame + if( aFrameOrObj.GetSwFrame() && mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xAccImpl = static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + if( !xAccImpl.is() && mpFrameMap ) + { + // If there is none, look if the parent is accessible. + const SwFrame *pParent = + SwAccessibleFrame::GetParent( aFrameOrObj, + GetShell()->IsPreview()); + + if( pParent ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( pParent ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xParentAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + } + if( !xParentAccImpl.is() && !aFrameOrObj.GetSwFrame() && mpShapeMap ) + { + SwAccessibleShapeMap_Impl::iterator aIter = + mpShapeMap->find( aFrameOrObj.GetDrawObject() ); + if( aIter != mpShapeMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xShapeAccImpl = + static_cast< ::accessibility::AccessibleShape *>( xAcc.get() ); + } + } + if( pObj && GetShell()->ActionPend() && + (xParentAccImpl.is() || xShapeAccImpl.is()) ) + { + // Keep a reference to the XShape to avoid that it + // is deleted with a SwFrameFormat::SwClientNotify. + uno::Reference < drawing::XShape > xShape( + const_cast< SdrObject * >( pObj )->getUnoShape(), + uno::UNO_QUERY ); + if( xShape.is() ) + { + mvShapes.push_back( xShape ); + } + } + } + + // remove events stored for the frame + { + osl::MutexGuard aGuard( maEventMutex ); + if( mpEvents ) + { + SwAccessibleEventMap_Impl::iterator aIter = + mpEventMap->find( aFrameOrObj ); + if( aIter != mpEventMap->end() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::DISPOSE, aFrameOrObj ); + AppendEvent( aEvent ); + } + } + } + + // If the frame is accessible and there is a context for it, dispose + // the frame. If the frame is no context for it but disposing should + // take place recursive, the frame's children have to be disposed + // anyway, so we have to create the context then. + if( xAccImpl.is() ) + { + xAccImpl->Dispose( bRecursive ); + } + else if( xParentAccImpl.is() ) + { + // If the frame is a cell frame, the table must be notified. + // If we are in an action, a table model change event will + // be broadcasted at the end of the action to give the table + // a chance to generate a single table change event. + + xParentAccImpl->DisposeChild( aFrameOrObj, bRecursive, bCanSkipInvisible ); + } + else if( xShapeAccImpl.is() ) + { + RemoveContext( aFrameOrObj.GetDrawObject() ); + xShapeAccImpl->dispose(); + } + + if( mpPreview && pFrame && pFrame->IsPageFrame() ) + mpPreview->DisposePage( static_cast< const SwPageFrame *>( pFrame ) ); +} + +void SwAccessibleMap::InvalidatePosOrSize( const SwFrame *pFrame, + const SdrObject *pObj, + vcl::Window* pWindow, + const SwRect& rOldBox ) +{ + SwAccessibleChild aFrameOrObj( pFrame, pObj, pWindow ); + if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + return; + + ::rtl::Reference< SwAccessibleContext > xAccImpl; + ::rtl::Reference< SwAccessibleContext > xParentAccImpl; + const SwFrame *pParent =nullptr; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + if( aFrameOrObj.GetSwFrame() ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + // If there is an accessible object already it is + // notified directly. + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + if( !xAccImpl.is() ) + { + // Otherwise we look if the parent is accessible. + // If not, there is nothing to do. + pParent = + SwAccessibleFrame::GetParent( aFrameOrObj, + GetShell()->IsPreview()); + + if( pParent ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( pParent ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xParentAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + } + } + } + + if( xAccImpl.is() ) + { + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::POS_CHANGED, xAccImpl.get(), + aFrameOrObj, rOldBox ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + if (xAccImpl->GetFrame()) // not if disposed by FireEvents() + { + xAccImpl->InvalidatePosOrSize(rOldBox); + } + } + } + else if( xParentAccImpl.is() ) + { + if( GetShell()->ActionPend() ) + { + assert(pParent); + // tdf#99722 faster not to buffer events that won't be sent + if (!SwAccessibleChild(pParent).IsVisibleChildrenOnly() + || xParentAccImpl->IsShowing(rOldBox) + || xParentAccImpl->IsShowing(*this, aFrameOrObj)) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + xParentAccImpl.get(), aFrameOrObj, rOldBox ); + AppendEvent( aEvent ); + } + } + else + { + FireEvents(); + xParentAccImpl->InvalidateChildPosOrSize( aFrameOrObj, + rOldBox ); + } + } + else if(pParent) + { +/* +For child graphic and its parent paragraph,if split 2 graphic to 2 paragraph, +will delete one graphic swfrm and new create 1 graphic swfrm , +then the new paragraph and the new graphic SwFrame will add . +but when add graphic SwFrame ,the accessible of the new Paragraph is not created yet. +so the new graphic accessible 'parent is NULL, +so run here: save the parent's SwFrame not the accessible object parent, +*/ + bool bIsValidFrame = false; + bool bIsTextParent = false; + if (aFrameOrObj.GetSwFrame()) + { + if (SwFrameType::Fly == pFrame->GetType()) + { + bIsValidFrame =true; + } + } + else if(pObj) + { + if (SwFrameType::Txt == pParent->GetType()) + { + bIsTextParent =true; + } + } + if( bIsValidFrame || bIsTextParent ) + { + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + pParent, aFrameOrObj, rOldBox ); + AppendEvent( aEvent ); + } + else + { + OSL_ENSURE(false,""); + } + } + } +} + +void SwAccessibleMap::InvalidateContent( const SwFrame *pFrame ) +{ + SwAccessibleChild aFrameOrObj( pFrame ); + if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + return; + + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + } + } + + if( !xAcc.is() ) + return; + + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::INVALID_CONTENT, pAccImpl, + aFrameOrObj ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateContent(); + } +} + +void SwAccessibleMap::InvalidateAttr( const SwTextFrame& rTextFrame ) +{ + SwAccessibleChild aFrameOrObj( &rTextFrame ); + if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + return; + + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + } + } + + if( !xAcc.is() ) + return; + + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::INVALID_ATTR, + pAccImpl, aFrameOrObj ); + aEvent.SetStates( AccessibleStates::TEXT_ATTRIBUTE_CHANGED ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateAttr(); + } +} + +void SwAccessibleMap::InvalidateCursorPosition( const SwFrame *pFrame ) +{ + SwAccessibleChild aFrameOrObj( pFrame ); + bool bShapeSelected = false; + const SwViewShell *pVSh = GetShell(); + if( auto pCSh = dynamic_cast<const SwCursorShell*>(pVSh) ) + { + if( pCSh->IsTableMode() ) + { + while( aFrameOrObj.GetSwFrame() && !aFrameOrObj.GetSwFrame()->IsCellFrame() ) + aFrameOrObj = aFrameOrObj.GetSwFrame()->GetUpper(); + } + else if( auto pFESh = dynamic_cast<const SwFEShell*>(pVSh) ) + { + const SwFrame *pFlyFrame = pFESh->GetSelectedFlyFrame(); + if( pFlyFrame ) + { + OSL_ENSURE( !pFrame || pFrame->FindFlyFrame() == pFlyFrame, + "cursor is not contained in fly frame" ); + aFrameOrObj = pFlyFrame; + } + else if( pFESh->IsObjSelected() > 0 ) + { + bShapeSelected = true; + aFrameOrObj = static_cast<const SwFrame *>( nullptr ); + } + } + } + + OSL_ENSURE( bShapeSelected || aFrameOrObj.IsAccessible(GetShell()->IsPreview()), + "frame is not accessible" ); + + uno::Reference < XAccessible > xOldAcc; + uno::Reference < XAccessible > xAcc; + bool bOldShapeSelected = false; + + { + osl::MutexGuard aGuard( maMutex ); + + xOldAcc = mxCursorContext; + mxCursorContext = xAcc; // clear reference + + bOldShapeSelected = mbShapeSelected; + mbShapeSelected = bShapeSelected; + + if( aFrameOrObj.GetSwFrame() && mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + else + { + SwRect rcEmpty; + const SwTabFrame* pTabFrame = aFrameOrObj.GetSwFrame()->FindTabFrame(); + if (pTabFrame) + { + InvalidatePosOrSize(pTabFrame, nullptr, nullptr, rcEmpty); + } + else + { + InvalidatePosOrSize(aFrameOrObj.GetSwFrame(), nullptr, nullptr, rcEmpty); + } + + aIter = mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + xAcc = (*aIter).second; + } + } + + // For cells, some extra thoughts are necessary, + // because invalidating the cursor for one cell + // invalidates the cursor for all cells of the same + // table. For this reason, we don't want to + // invalidate the cursor for the old cursor object + // and the new one if they are within the same table, + // because this would result in doing the work twice. + // Moreover, we have to make sure to invalidate the + // cursor even if the current cell has no accessible object. + // If the old cursor objects exists and is in the same + // table, it's the best choice, because using it avoids + // an unnecessary cursor invalidation cycle when creating + // a new object for the current cell. + if( aFrameOrObj.GetSwFrame()->IsCellFrame() ) + { + if( xOldAcc.is() && + AreInSameTable( xOldAcc, aFrameOrObj.GetSwFrame() ) ) + { + if( xAcc.is() ) + xOldAcc = xAcc; // avoid extra invalidation + else + xAcc = xOldAcc; // make sure at least one + } + if( !xAcc.is() ) + xAcc = GetContext( aFrameOrObj.GetSwFrame() ); + } + } + else if (bShapeSelected) + { + const SwFEShell *pFESh = static_cast< const SwFEShell * >( pVSh ); + const SdrMarkList *pMarkList = pFESh->GetMarkList(); + if (pMarkList != nullptr && pMarkList->GetMarkCount() == 1) + { + SdrObject *pObj = pMarkList->GetMark( 0 )->GetMarkedSdrObj(); + ::rtl::Reference < ::accessibility::AccessibleShape > pAccShapeImpl = GetContextImpl(pObj,nullptr,false); + if (!pAccShapeImpl.is()) + { + while (pObj && pObj->getParentSdrObjectFromSdrObject()) + { + pObj = pObj->getParentSdrObjectFromSdrObject(); + } + if (pObj != nullptr) + { + const SwFrame *pParent = SwAccessibleFrame::GetParent( SwAccessibleChild(pObj), GetShell()->IsPreview() ); + if( pParent ) + { + ::rtl::Reference< SwAccessibleContext > xParentAccImpl = GetContextImpl(pParent,false); + if (!xParentAccImpl.is()) + { + const SwTabFrame* pTabFrame = pParent->FindTabFrame(); + if (pTabFrame) + { + //The Table should not add in acc.because the "pParent" is not add to acc . + uno::Reference< XAccessible> xAccParentTab = GetContext(pTabFrame);//Should Create. + + const SwFrame *pParentRoot = SwAccessibleFrame::GetParent( SwAccessibleChild(pTabFrame), GetShell()->IsPreview() ); + if (pParentRoot) + { + ::rtl::Reference< SwAccessibleContext > xParentAccImplRoot = GetContextImpl(pParentRoot,false); + if(xParentAccImplRoot.is()) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= xAccParentTab; + xParentAccImplRoot->FireAccessibleEvent( aEvent ); + } + } + + //Get "pParent" acc again. + xParentAccImpl = GetContextImpl(pParent,false); + } + else + { + //directly create this acc para . + xParentAccImpl = GetContextImpl(pParent);//Should Create. + + const SwFrame *pParentRoot = SwAccessibleFrame::GetParent( SwAccessibleChild(pParent), GetShell()->IsPreview() ); + + ::rtl::Reference< SwAccessibleContext > xParentAccImplRoot = GetContextImpl(pParentRoot,false); + if(xParentAccImplRoot.is()) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= uno::Reference< XAccessible>(xParentAccImpl); + xParentAccImplRoot->FireAccessibleEvent( aEvent ); + } + } + } + if (xParentAccImpl.is()) + { + uno::Reference< XAccessible> xAccShape = + GetContext(pObj,xParentAccImpl.get()); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= xAccShape; + xParentAccImpl->FireAccessibleEvent( aEvent ); + } + } + } + } + } + } + } + + m_setParaAdd.clear(); + m_setParaRemove.clear(); + if( xOldAcc.is() && xOldAcc != xAcc ) + InvalidateCursorPosition( xOldAcc ); + if( bOldShapeSelected || bShapeSelected ) + InvalidateShapeSelection(); + if( xAcc.is() ) + InvalidateCursorPosition( xAcc ); + + InvalidateShapeInParaSelection(); + + for (SwAccessibleParagraph* pAccPara : m_setParaRemove) + { + if(pAccPara && pAccPara->getSelectedAccessibleChildCount() == 0 && pAccPara->getSelectedText().getLength() == 0) + { + if(pAccPara->SetSelectedState(false)) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE; + pAccPara->FireAccessibleEvent( aEvent ); + } + } + } + for (SwAccessibleParagraph* pAccPara : m_setParaAdd) + { + if(pAccPara && pAccPara->SetSelectedState(true)) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + pAccPara->FireAccessibleEvent( aEvent ); + } + } +} + +void SwAccessibleMap::InvalidateFocus() +{ + if(GetShell()->IsPreview()) + { + uno::Reference<XAccessible> xAcc = GetDocumentView_( true ); + if (xAcc) + { + SwAccessiblePreview *pAccPreview = static_cast<SwAccessiblePreview *>(xAcc.get()); + if (pAccPreview) + { + pAccPreview->InvalidateFocus(); + return ; + } + } + } + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + xAcc = mxCursorContext; + } + + if( xAcc.is() ) + { + SwAccessibleContext *pAccImpl = static_cast< SwAccessibleContext *>( xAcc.get() ); + pAccImpl->InvalidateFocus(); + } + else + { + DoInvalidateShapeSelection(true); + } +} + +void SwAccessibleMap::SetCursorContext( + const ::rtl::Reference < SwAccessibleContext >& rCursorContext ) +{ + osl::MutexGuard aGuard( maMutex ); + uno::Reference < XAccessible > xAcc( rCursorContext ); + mxCursorContext = xAcc; +} + +void SwAccessibleMap::InvalidateEditableStates( const SwFrame* _pFrame ) +{ + // Start with the frame or the first upper that is accessible + SwAccessibleChild aFrameOrObj( _pFrame ); + while( aFrameOrObj.GetSwFrame() && + !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + aFrameOrObj = aFrameOrObj.GetSwFrame()->GetUpper(); + if( !aFrameOrObj.GetSwFrame() ) + aFrameOrObj = GetShell()->GetLayout(); + + uno::Reference< XAccessible > xAcc( GetContext( aFrameOrObj.GetSwFrame() ) ); + SwAccessibleContext *pAccImpl = static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::CARET_OR_STATES, + pAccImpl, + SwAccessibleChild(pAccImpl->GetFrame()), + AccessibleStates::EDITABLE ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateStates( AccessibleStates::EDITABLE ); + } +} + +void SwAccessibleMap::InvalidateRelationSet_( const SwFrame* pFrame, + bool bFrom ) +{ + // first, see if this frame is accessible, and if so, get the respective + SwAccessibleChild aFrameOrObj( pFrame ); + if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + return; + + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + xAcc = (*aIter).second; + } + } + } + + // deliver event directly, or queue event + if( !xAcc.is() ) + return; + + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::CARET_OR_STATES, + pAccImpl, SwAccessibleChild(pFrame), + ( bFrom + ? AccessibleStates::RELATION_FROM + : AccessibleStates::RELATION_TO ) ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateRelation( bFrom + ? AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED + : AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED ); + } +} + +void SwAccessibleMap::InvalidateRelationSet( const SwFrame* pMaster, + const SwFrame* pFollow ) +{ + InvalidateRelationSet_( pMaster, false ); + InvalidateRelationSet_( pFollow, true ); +} + +// invalidation of CONTENT_FLOW_FROM/_TO relation of a paragraph +void SwAccessibleMap::InvalidateParaFlowRelation( const SwTextFrame& _rTextFrame, + const bool _bFrom ) +{ + InvalidateRelationSet_( &_rTextFrame, _bFrom ); +} + +// invalidation of text selection of a paragraph +void SwAccessibleMap::InvalidateParaTextSelection( const SwTextFrame& _rTextFrame ) +{ + // first, see if this frame is accessible, and if so, get the respective + SwAccessibleChild aFrameOrObj( &_rTextFrame ); + if( !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + return; + + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + xAcc = (*aIter).second; + } + } + } + + // deliver event directly, or queue event + if( !xAcc.is() ) + return; + + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::CARET_OR_STATES, + pAccImpl, + SwAccessibleChild( &_rTextFrame ), + AccessibleStates::TEXT_SELECTION_CHANGED ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateTextSelection(); + } +} + +sal_Int32 SwAccessibleMap::GetChildIndex( const SwFrame& rParentFrame, + vcl::Window& rChild ) const +{ + sal_Int32 nIndex( -1 ); + + SwAccessibleChild aFrameOrObj( &rParentFrame ); + if( aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + { + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + xAcc = (*aIter).second; + } + } + } + + if( xAcc.is() ) + { + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + + nIndex = pAccImpl->GetChildIndex( const_cast<SwAccessibleMap&>(*this), + SwAccessibleChild( &rChild ) ); + } + } + + return nIndex; +} + +void SwAccessibleMap::UpdatePreview( const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ) +{ + assert(GetShell()->IsPreview() && "no preview?"); + assert(mpPreview != nullptr && "no preview data?"); + + mpPreview->Update( *this, _rPreviewPages, _rScale, _pSelectedPageFrame, _rPreviewWinSize ); + + // propagate change of VisArea through the document's + // accessibility tree; this will also send appropriate scroll + // events + SwAccessibleContext* pDoc = + GetContextImpl( GetShell()->GetLayout() ).get(); + static_cast<SwAccessibleDocumentBase*>( pDoc )->SetVisArea(); + + uno::Reference < XAccessible > xOldAcc; + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + xOldAcc = mxCursorContext; + + const SwPageFrame *pSelPage = mpPreview->GetSelPage(); + if( pSelPage && mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( pSelPage ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + } + } + + if( xOldAcc.is() && xOldAcc != xAcc ) + InvalidateCursorPosition( xOldAcc ); + if( xAcc.is() ) + InvalidateCursorPosition( xAcc ); +} + +void SwAccessibleMap::InvalidatePreviewSelection( sal_uInt16 nSelPage ) +{ + assert(GetShell()->IsPreview()); + assert(mpPreview != nullptr); + + mpPreview->InvalidateSelection( GetShell()->GetLayout()->GetPageByPageNum( nSelPage ) ); + + uno::Reference < XAccessible > xOldAcc; + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + xOldAcc = mxCursorContext; + + const SwPageFrame *pSelPage = mpPreview->GetSelPage(); + if( pSelPage && mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pSelPage ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + } + } + + if( xOldAcc.is() && xOldAcc != xAcc ) + InvalidateCursorPosition( xOldAcc ); + if( xAcc.is() ) + InvalidateCursorPosition( xAcc ); +} + +bool SwAccessibleMap::IsPageSelected( const SwPageFrame *pPageFrame ) const +{ + return mpPreview && mpPreview->GetSelPage() == pPageFrame; +} + +void SwAccessibleMap::FireEvents() +{ + { + osl::MutexGuard aGuard( maEventMutex ); + if( mpEvents ) + { + if (mpEvents->IsFiring()) + { + return; // prevent recursive FireEvents() + } + + mpEvents->SetFiring(); + mpEvents->MoveMissingXAccToEnd(); + for( auto const& aEvent : *mpEvents ) + FireEvent(aEvent); + + mpEventMap.reset(); + mpEvents.reset(); + } + } + { + osl::MutexGuard aGuard( maMutex ); + mvShapes.clear(); + } + +} + +tools::Rectangle SwAccessibleMap::GetVisibleArea() const +{ + return o3tl::convert( GetVisArea().SVRect(), o3tl::Length::twip, o3tl::Length::mm100 ); +} + +// Convert a MM100 value relative to the document root into a pixel value +// relative to the screen! +Point SwAccessibleMap::LogicToPixel( const Point& rPoint ) const +{ + Point aPoint = o3tl::toTwips( rPoint, o3tl::Length::mm100 ); + if (const vcl::Window* pWin = GetShell()->GetWin()) + { + MapMode aMapMode; + GetMapMode( aPoint, aMapMode ); + aPoint = pWin->LogicToPixel( aPoint, aMapMode ); + aPoint = pWin->OutputToAbsoluteScreenPixel( aPoint ); + } + + return aPoint; +} + +Size SwAccessibleMap::LogicToPixel( const Size& rSize ) const +{ + Size aSize( o3tl::toTwips( rSize, o3tl::Length::mm100 ) ); + if (const OutputDevice* pWin = GetShell()->GetWin()->GetOutDev()) + { + MapMode aMapMode; + GetMapMode( Point(0,0), aMapMode ); + aSize = pWin->LogicToPixel( aSize, aMapMode ); + } + + return aSize; +} + +bool SwAccessibleMap::ReplaceChild ( + ::accessibility::AccessibleShape* pCurrentChild, + const uno::Reference< drawing::XShape >& _rxShape, + const tools::Long /*_nIndex*/, + const ::accessibility::AccessibleShapeTreeInfo& /*_rShapeTreeInfo*/ + ) +{ + const SdrObject *pObj = nullptr; + { + osl::MutexGuard aGuard( maMutex ); + if( mpShapeMap ) + { + SwAccessibleShapeMap_Impl::const_iterator aIter = mpShapeMap->cbegin(); + SwAccessibleShapeMap_Impl::const_iterator aEndIter = mpShapeMap->cend(); + while( aIter != aEndIter && !pObj ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + ::accessibility::AccessibleShape *pAccShape = + static_cast < ::accessibility::AccessibleShape* >( xAcc.get() ); + if( pAccShape == pCurrentChild ) + { + pObj = (*aIter).first; + } + ++aIter; + } + } + } + if( !pObj ) + return false; + + uno::Reference < drawing::XShape > xShape( _rxShape ); // keep reference to shape, because + // we might be the only one that + // holds it. + // Also get keep parent. + uno::Reference < XAccessible > xParent( pCurrentChild->getAccessibleParent() ); + pCurrentChild = nullptr; // will be released by dispose + A11yDispose( nullptr, pObj, nullptr ); + + { + osl::MutexGuard aGuard( maMutex ); + + if( !mpShapeMap ) + mpShapeMap.reset(new SwAccessibleShapeMap_Impl( this )); + + // create the new child + ::accessibility::ShapeTypeHandler& rShapeTypeHandler = + ::accessibility::ShapeTypeHandler::Instance(); + ::accessibility::AccessibleShapeInfo aShapeInfo( + xShape, xParent, this ); + rtl::Reference< ::accessibility::AccessibleShape> pReplacement( + rShapeTypeHandler.CreateAccessibleObject ( + aShapeInfo, mpShapeMap->GetInfo() )); + + uno::Reference < XAccessible > xAcc( pReplacement ); + if( xAcc.is() ) + { + pReplacement->Init(); + + SwAccessibleShapeMap_Impl::iterator aIter = mpShapeMap->find( pObj ); + if( aIter != mpShapeMap->end() ) + { + (*aIter).second = xAcc; + } + else + { + mpShapeMap->emplace( pObj, xAcc ); + } + } + } + + SwRect aEmptyRect; + InvalidatePosOrSize( nullptr, pObj, nullptr, aEmptyRect ); + + return true; +} + +//Get the accessible control shape from the model object, here model object is with XPropertySet type +::accessibility::AccessibleControlShape * SwAccessibleMap::GetAccControlShapeFromModel(css::beans::XPropertySet* pSet) +{ + if( mpShapeMap ) + { + SwAccessibleShapeMap_Impl::const_iterator aIter = mpShapeMap->cbegin(); + SwAccessibleShapeMap_Impl::const_iterator aEndIter = mpShapeMap->cend(); + while( aIter != aEndIter) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + ::accessibility::AccessibleShape *pAccShape = + static_cast < ::accessibility::AccessibleShape* >( xAcc.get() ); + if(pAccShape && ::accessibility::ShapeTypeHandler::Instance().GetTypeId (pAccShape->GetXShape()) == ::accessibility::DRAWING_CONTROL) + { + ::accessibility::AccessibleControlShape *pCtlAccShape = static_cast < ::accessibility::AccessibleControlShape* >(pAccShape); + if (pCtlAccShape->GetControlModel() == pSet) + return pCtlAccShape; + } + ++aIter; + } + } + return nullptr; +} + +css::uno::Reference< XAccessible > + SwAccessibleMap::GetAccessibleCaption (const css::uno::Reference< css::drawing::XShape >&) +{ + return nullptr; +} + +Point SwAccessibleMap::PixelToCore( const Point& rPoint ) const +{ + Point aPoint; + if (const OutputDevice* pWin = GetShell()->GetWin()->GetOutDev()) + { + MapMode aMapMode; + GetMapMode( rPoint, aMapMode ); + aPoint = pWin->PixelToLogic( rPoint, aMapMode ); + } + return aPoint; +} + +static tools::Long lcl_CorrectCoarseValue(tools::Long aCoarseValue, tools::Long aFineValue, + tools::Long aRefValue, bool bToLower) +{ + tools::Long aResult = aCoarseValue; + + if (bToLower) + { + if (aFineValue < aRefValue) + aResult -= 1; + } + else + { + if (aFineValue > aRefValue) + aResult += 1; + } + + return aResult; +} + +static void lcl_CorrectRectangle(tools::Rectangle & rRect, + const tools::Rectangle & rSource, + const tools::Rectangle & rInGrid) +{ + rRect.SetLeft( lcl_CorrectCoarseValue(rRect.Left(), rSource.Left(), + rInGrid.Left(), false) ); + rRect.SetTop( lcl_CorrectCoarseValue(rRect.Top(), rSource.Top(), + rInGrid.Top(), false) ); + rRect.SetRight( lcl_CorrectCoarseValue(rRect.Right(), rSource.Right(), + rInGrid.Right(), true) ); + rRect.SetBottom( lcl_CorrectCoarseValue(rRect.Bottom(), rSource.Bottom(), + rInGrid.Bottom(), true) ); +} + +tools::Rectangle SwAccessibleMap::CoreToPixel( const SwRect& rRect ) const +{ + tools::Rectangle aRect; + if (const OutputDevice* pWin = GetShell()->GetWin()->GetOutDev()) + { + MapMode aMapMode; + GetMapMode( rRect.TopLeft(), aMapMode ); + aRect = pWin->LogicToPixel( rRect.SVRect(), aMapMode ); + + tools::Rectangle aTmpRect = pWin->PixelToLogic( aRect, aMapMode ); + lcl_CorrectRectangle(aRect, rRect.SVRect(), aTmpRect); + } + + return aRect; +} + +/** get mapping mode for LogicToPixel and PixelToLogic conversions + + Method returns mapping mode of current output device and adjusts it, + if the shell is in page/print preview. + Necessary, because <PreviewAdjust(..)> changes mapping mode at current + output device for mapping logic document positions to page preview window + positions and vice versa and doesn't take care to recover its changes. +*/ +void SwAccessibleMap::GetMapMode( const Point& _rPoint, + MapMode& _orMapMode ) const +{ + MapMode aMapMode = GetShell()->GetWin()->GetMapMode(); + if( GetShell()->IsPreview() ) + { + assert(mpPreview != nullptr); + mpPreview->AdjustMapMode( aMapMode, _rPoint ); + } + _orMapMode = aMapMode; +} + +Size SwAccessibleMap::GetPreviewPageSize(sal_uInt16 const nPreviewPageNum) const +{ + assert(mpVSh->IsPreview()); + assert(mpPreview != nullptr); + return mpVSh->PagePreviewLayout()->GetPreviewPageSizeByPageNum(nPreviewPageNum); +} + +/** method to build up a new data structure of the accessible paragraphs, + which have a selection + Important note: method has to be used inside a mutual exclusive section +*/ +std::unique_ptr<SwAccessibleSelectedParas_Impl> SwAccessibleMap::BuildSelectedParas() +{ + // no accessible contexts, no selection + if ( !mpFrameMap ) + { + return nullptr; + } + + // get cursor as an instance of its base class <SwPaM> + SwPaM* pCursor( nullptr ); + { + SwCursorShell* pCursorShell = dynamic_cast<SwCursorShell*>(GetShell()); + if ( pCursorShell ) + { + SwFEShell* pFEShell = dynamic_cast<SwFEShell*>(pCursorShell); + if ( !pFEShell || + ( !pFEShell->IsFrameSelected() && + pFEShell->IsObjSelected() == 0 ) ) + { + // get cursor without updating an existing table cursor. + pCursor = pCursorShell->GetCursor( false ); + } + } + } + // no cursor, no selection + if ( !pCursor ) + { + return nullptr; + } + + std::unique_ptr<SwAccessibleSelectedParas_Impl> pRetSelectedParas; + + // loop on all cursors + SwPaM* pRingStart = pCursor; + do { + + // for a selection the cursor has to have a mark. + // for safety reasons assure that point and mark are in text nodes + if ( pCursor->HasMark() && + pCursor->GetPoint()->nNode.GetNode().IsTextNode() && + pCursor->GetMark()->nNode.GetNode().IsTextNode() ) + { + SwPosition* pStartPos = pCursor->Start(); + SwPosition* pEndPos = pCursor->End(); + // loop on all text nodes inside the selection + SwNodeIndex aIdx( pStartPos->nNode ); + for ( ; aIdx.GetIndex() <= pEndPos->nNode.GetIndex(); ++aIdx ) + { + SwTextNode* pTextNode( aIdx.GetNode().GetTextNode() ); + if ( pTextNode ) + { + // loop on all text frames registered at the text node. + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pTextNode); + for( SwTextFrame* pTextFrame = aIter.First(); pTextFrame; pTextFrame = aIter.Next() ) + { + uno::WeakReference < XAccessible > xWeakAcc; + SwAccessibleContextMap_Impl::iterator aMapIter = + mpFrameMap->find( pTextFrame ); + if( aMapIter != mpFrameMap->end() ) + { + xWeakAcc = (*aMapIter).second; + SwAccessibleParaSelection aDataEntry( + sw::FrameContainsNode(*pTextFrame, pStartPos->nNode.GetIndex()) + ? pTextFrame->MapModelToViewPos(*pStartPos) + : TextFrameIndex(0), + + sw::FrameContainsNode(*pTextFrame, pEndPos->nNode.GetIndex()) + ? pTextFrame->MapModelToViewPos(*pEndPos) + : TextFrameIndex(COMPLETE_STRING)); + if ( !pRetSelectedParas ) + { + pRetSelectedParas.reset( + new SwAccessibleSelectedParas_Impl); + } + // sw_redlinehide: should be idempotent for multiple nodes in a merged para + pRetSelectedParas->emplace( xWeakAcc, aDataEntry ); + } + } + } + } + } + + // prepare next turn: get next cursor in ring + pCursor = pCursor->GetNext(); + } while ( pCursor != pRingStart ); + + return pRetSelectedParas; +} + +void SwAccessibleMap::InvalidateTextSelectionOfAllParas() +{ + osl::MutexGuard aGuard( maMutex ); + + // keep previously known selected paragraphs + std::unique_ptr<SwAccessibleSelectedParas_Impl> pPrevSelectedParas( std::move(mpSelectedParas) ); + + // determine currently selected paragraphs + mpSelectedParas = BuildSelectedParas(); + + // compare currently selected paragraphs with the previously selected + // paragraphs and submit corresponding TEXT_SELECTION_CHANGED events. + // first, search for new and changed selections. + // on the run remove selections from previously known ones, if they are + // also in the current ones. + if ( mpSelectedParas ) + { + SwAccessibleSelectedParas_Impl::iterator aIter = mpSelectedParas->begin(); + for ( ; aIter != mpSelectedParas->end(); ++aIter ) + { + bool bSubmitEvent( false ); + if ( !pPrevSelectedParas ) + { + // new selection + bSubmitEvent = true; + } + else + { + SwAccessibleSelectedParas_Impl::iterator aPrevSelected = + pPrevSelectedParas->find( (*aIter).first ); + if ( aPrevSelected != pPrevSelectedParas->end() ) + { + // check, if selection has changed + if ( (*aIter).second.nStartOfSelection != + (*aPrevSelected).second.nStartOfSelection || + (*aIter).second.nEndOfSelection != + (*aPrevSelected).second.nEndOfSelection ) + { + // changed selection + bSubmitEvent = true; + } + pPrevSelectedParas->erase( aPrevSelected ); + } + else + { + // new selection + bSubmitEvent = true; + } + } + + if ( bSubmitEvent ) + { + uno::Reference < XAccessible > xAcc( (*aIter).first ); + if ( xAcc.is() ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl( + static_cast<SwAccessibleContext*>( xAcc.get() ) ); + if ( xAccImpl.is() && xAccImpl->GetFrame() ) + { + const SwTextFrame* pTextFrame = xAccImpl->GetFrame()->DynCastTextFrame(); + OSL_ENSURE( pTextFrame, + "<SwAccessibleMap::_SubmitTextSelectionChangedEvents()> - unexpected type of frame" ); + if ( pTextFrame ) + { + InvalidateParaTextSelection( *pTextFrame ); + } + } + } + } + } + } + + // second, handle previous selections - after the first step the data + // structure of the previously known only contains the 'old' selections + if ( !pPrevSelectedParas ) + return; + + SwAccessibleSelectedParas_Impl::iterator aIter = pPrevSelectedParas->begin(); + for ( ; aIter != pPrevSelectedParas->end(); ++aIter ) + { + uno::Reference < XAccessible > xAcc( (*aIter).first ); + if ( xAcc.is() ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl( + static_cast<SwAccessibleContext*>( xAcc.get() ) ); + if ( xAccImpl.is() && xAccImpl->GetFrame() ) + { + const SwTextFrame* pTextFrame = xAccImpl->GetFrame()->DynCastTextFrame(); + OSL_ENSURE( pTextFrame, + "<SwAccessibleMap::_SubmitTextSelectionChangedEvents()> - unexpected type of frame" ); + if ( pTextFrame ) + { + InvalidateParaTextSelection( *pTextFrame ); + } + } + } + } +} + +const SwRect& SwAccessibleMap::GetVisArea() const +{ + assert(!GetShell()->IsPreview() || (mpPreview != nullptr)); + + return GetShell()->IsPreview() + ? mpPreview->GetVisArea() + : GetShell()->VisArea(); +} + +bool SwAccessibleMap::IsDocumentSelAll() +{ + return GetShell()->GetDoc()->IsPrepareSelAll(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accnotextframe.cxx b/sw/source/core/access/accnotextframe.cxx new file mode 100644 index 000000000..0f1454602 --- /dev/null +++ b/sw/source/core/access/accnotextframe.cxx @@ -0,0 +1,315 @@ +/* -*- 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 . + */ + +#include <vcl/svapp.hxx> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <cppuhelper/typeprovider.hxx> +#include <frmfmt.hxx> +#include <ndnotxt.hxx> +#include <flyfrm.hxx> +#include <notxtfrm.hxx> +#include <hints.hxx> +#include "accnotextframe.hxx" +#include <fmturl.hxx> +#include "accnotexthyperlink.hxx" +#include <unotools/accessiblerelationsethelper.hxx> +#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using utl::AccessibleRelationSetHelper; + +const SwNoTextNode *SwAccessibleNoTextFrame::GetNoTextNode() const +{ + const SwNoTextNode *pNd = nullptr; + const SwFlyFrame *pFlyFrame = static_cast< const SwFlyFrame *>( GetFrame() ); + if( pFlyFrame->Lower() && pFlyFrame->Lower()->IsNoTextFrame() ) + { + const SwNoTextFrame *pContentFrame = + static_cast<const SwNoTextFrame *>(pFlyFrame->Lower()); + const SwContentNode* pSwContentNode = pContentFrame->GetNode(); + if(pSwContentNode != nullptr) + { + pNd = pSwContentNode->GetNoTextNode(); + } + } + + return pNd; +} + +SwAccessibleNoTextFrame::SwAccessibleNoTextFrame( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + sal_Int16 nInitRole, + const SwFlyFrame* pFlyFrame ) : + SwAccessibleFrameBase( pInitMap, nInitRole, pFlyFrame ) +{ + const SwNoTextNode* pNd = GetNoTextNode(); + // #i73249# + // consider new attributes Title and Description + if( pNd ) + { + StartListening(const_cast<SwNoTextNode*>(pNd)->GetNotifier()); + msTitle = pNd->GetTitle(); + + msDesc = pNd->GetDescription(); + if ( msDesc.isEmpty() && + msTitle != GetName() ) + { + msDesc = msTitle; + } + } +} + +SwAccessibleNoTextFrame::~SwAccessibleNoTextFrame() +{ +} + +void SwAccessibleNoTextFrame::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + EndListeningAll(); + else if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacyModifyHint = static_cast<const sw::LegacyModifyHint*>(&rHint); + const sal_uInt16 nWhich = pLegacyModifyHint->GetWhich(); + if (nWhich != RES_TITLE_CHANGED && nWhich != RES_DESCRIPTION_CHANGED) + return; + const SwNoTextNode* pNd = GetNoTextNode(); + switch(nWhich) + { + // #i73249# + case RES_TITLE_CHANGED: + { + OUString sOldTitle, sNewTitle; + const SwStringMsgPoolItem* pOldItem = dynamic_cast<const SwStringMsgPoolItem*>(pLegacyModifyHint->m_pOld); + if(pOldItem) + sOldTitle = pOldItem->GetString(); + const SwStringMsgPoolItem* pNewItem = dynamic_cast<const SwStringMsgPoolItem*>(pLegacyModifyHint->m_pNew); + if(pNewItem) + sNewTitle = pNewItem->GetString(); + if(sOldTitle == sNewTitle) + break; + msTitle = sNewTitle; + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + aEvent.OldValue <<= sOldTitle; + aEvent.NewValue <<= msTitle; + FireAccessibleEvent(aEvent); + + if(!pNd->GetDescription().isEmpty()) + break; + [[fallthrough]]; + } + case RES_DESCRIPTION_CHANGED: + { + if(pNd && GetFrame()) + { + const OUString sOldDesc(msDesc); + + const OUString& rDesc = pNd->GetDescription(); + msDesc = rDesc; + if(msDesc.isEmpty() && msTitle != GetName()) + msDesc = msTitle; + + if(msDesc != sOldDesc) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED; + aEvent.OldValue <<= sOldDesc; + aEvent.NewValue <<= msDesc; + FireAccessibleEvent(aEvent); + } + } + } + } + } +} + +void SwAccessibleNoTextFrame::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + SolarMutexGuard aGuard; + EndListeningAll(); + SwAccessibleFrameBase::Dispose(bRecursive, bCanSkipInvisible); +} + +// #i73249# +OUString SAL_CALL SwAccessibleNoTextFrame::getAccessibleName() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if ( !msTitle.isEmpty() ) + { + return msTitle; + } + + return SwAccessibleFrameBase::getAccessibleName(); +} + +OUString SAL_CALL SwAccessibleNoTextFrame::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return msDesc; +} + +// XInterface + +uno::Any SAL_CALL SwAccessibleNoTextFrame::queryInterface( const uno::Type& aType ) +{ + if( aType == + ::cppu::UnoType<XAccessibleImage>::get() ) + { + uno::Reference<XAccessibleImage> xImage = this; + return uno::Any(xImage); + } + else if ( aType == cppu::UnoType<XAccessibleHypertext>::get()) + { + uno::Reference<XAccessibleHypertext> aAccHypertext = this; + return uno::Any( aAccHypertext ); + } + else + return SwAccessibleContext::queryInterface( aType ); +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SwAccessibleNoTextFrame::getTypes() +{ + return cppu::OTypeCollection( + ::cppu::UnoType<XAccessibleImage>::get(), + SwAccessibleFrameBase::getTypes() ).getTypes(); +} + +/// XAccessibleImage +/** implementation of the XAccessibleImage methods is a no-brainer, as + all relevant information is already accessible through other + methods. So we just delegate to those. */ + +OUString SAL_CALL SwAccessibleNoTextFrame::getAccessibleImageDescription() +{ + return getAccessibleDescription(); +} + +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getAccessibleImageHeight( ) +{ + return getSize().Height; +} + +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getAccessibleImageWidth( ) +{ + return getSize().Width; +} + +// XAccessibleText +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getCaretPosition( ){return 0;} +sal_Bool SAL_CALL SwAccessibleNoTextFrame::setCaretPosition( sal_Int32 ){return false;} +sal_Unicode SAL_CALL SwAccessibleNoTextFrame::getCharacter( sal_Int32 ){return 0;} +css::uno::Sequence< css::beans::PropertyValue > SAL_CALL SwAccessibleNoTextFrame::getCharacterAttributes( sal_Int32 , const css::uno::Sequence< OUString >& ) +{ + return uno::Sequence<beans::PropertyValue>(); +} +css::awt::Rectangle SAL_CALL SwAccessibleNoTextFrame::getCharacterBounds( sal_Int32 ) +{ + return css::awt::Rectangle(0, 0, 0, 0 ); +} +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getCharacterCount( ){return 0;} +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getIndexAtPoint( const css::awt::Point& ){return 0;} +OUString SAL_CALL SwAccessibleNoTextFrame::getSelectedText( ){return OUString();} +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getSelectionStart( ){return 0;} +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getSelectionEnd( ){return 0;} +sal_Bool SAL_CALL SwAccessibleNoTextFrame::setSelection( sal_Int32 , sal_Int32 ){return true;} +OUString SAL_CALL SwAccessibleNoTextFrame::getText( ){return OUString();} +OUString SAL_CALL SwAccessibleNoTextFrame::getTextRange( sal_Int32 , sal_Int32 ){return OUString();} +css::accessibility::TextSegment SAL_CALL SwAccessibleNoTextFrame::getTextAtIndex( sal_Int32 , sal_Int16 ) +{ + css::accessibility::TextSegment aResult; + return aResult; +} +css::accessibility::TextSegment SAL_CALL SwAccessibleNoTextFrame::getTextBeforeIndex( sal_Int32, sal_Int16 ) +{ + css::accessibility::TextSegment aResult; + return aResult; +} +css::accessibility::TextSegment SAL_CALL SwAccessibleNoTextFrame::getTextBehindIndex( sal_Int32 , sal_Int16 ) +{ + css::accessibility::TextSegment aResult; + return aResult; +} + +sal_Bool SAL_CALL SwAccessibleNoTextFrame::copyText( sal_Int32, sal_Int32 ){return true;} +sal_Bool SAL_CALL SwAccessibleNoTextFrame::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType ){return false;} + +// XAccessibleHyperText + +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getHyperLinkCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nCount = 0; + SwFormatURL aURL( static_cast<const SwLayoutFrame*>(GetFrame())->GetFormat()->GetURL() ); + + if(aURL.GetMap() || !aURL.GetURL().isEmpty()) + nCount = 1; + + return nCount; +} + +uno::Reference< XAccessibleHyperlink > SAL_CALL + SwAccessibleNoTextFrame::getHyperLink( sal_Int32 nLinkIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + SwFormatURL aURL( static_cast<const SwLayoutFrame*>(GetFrame())->GetFormat()->GetURL() ); + + if( nLinkIndex > 0 ) + throw lang::IndexOutOfBoundsException(); + + if( aURL.GetMap() || !aURL.GetURL().isEmpty() ) + { + if ( !m_xHyperlink.is() ) + { + m_xHyperlink = new SwAccessibleNoTextHyperlink( this, GetFrame() ); + } + + return m_xHyperlink; + } + + return nullptr; +} + +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getHyperLinkIndex( sal_Int32 ) +{ + return 0; +} + +uno::Reference<XAccessibleRelationSet> SAL_CALL SwAccessibleNoTextFrame::getAccessibleRelationSet( ) +{ + return new AccessibleRelationSetHelper(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accnotextframe.hxx b/sw/source/core/access/accnotextframe.hxx new file mode 100644 index 000000000..13680ffa5 --- /dev/null +++ b/sw/source/core/access/accnotextframe.hxx @@ -0,0 +1,125 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCNOTEXTFRAME_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCNOTEXTFRAME_HXX + +#include "accframebase.hxx" +#include <com/sun/star/accessibility/AccessibleScrollType.hpp> +#include <com/sun/star/accessibility/XAccessibleImage.hpp> +#include <com/sun/star/accessibility/XAccessibleHypertext.hpp> + +class SwFlyFrame; +class SwNoTextNode; + +class SwAccessibleNoTextFrame : public SwAccessibleFrameBase, + public css::accessibility::XAccessibleImage, + public css::accessibility::XAccessibleHypertext//Added by yangzhh for HyperLink +{ + friend class SwAccessibleNoTextHyperlink; + css::uno::Reference< css::accessibility::XAccessibleHyperlink > m_xHyperlink; + OUString msTitle; + OUString msDesc; + +protected: + virtual ~SwAccessibleNoTextFrame() override; + + const SwNoTextNode *GetNoTextNode() const; + + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleNoTextFrame( std::shared_ptr<SwAccessibleMap> const& pInitMap, + sal_Int16 nInitRole, + const SwFlyFrame *pFlyFrame ); + + // XAccessibleContext + + // #i73249# - Return the object's current name. + virtual OUString SAL_CALL + getAccessibleName() override; + + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XInterface methods need to be implemented to disambiguate + // between those inherited through SwAccessibleContext and + // XAccessibleImage. + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) noexcept override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) noexcept override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + + // XAccessibleImage + virtual OUString SAL_CALL + getAccessibleImageDescription( ) override; + + virtual sal_Int32 SAL_CALL + getAccessibleImageHeight( ) override; + + virtual sal_Int32 SAL_CALL + getAccessibleImageWidth( ) override; + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + + virtual sal_Int32 SAL_CALL getCaretPosition( ) override; + virtual sal_Bool SAL_CALL setCaretPosition( sal_Int32 nIndex ) override; + virtual sal_Unicode SAL_CALL getCharacter( sal_Int32 nIndex ) override;//Shen Zhen Jie changed sal_Unicode to sal_uInt32 + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) override; + virtual css::awt::Rectangle SAL_CALL getCharacterBounds( sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getCharacterCount( ) override; + virtual sal_Int32 SAL_CALL getIndexAtPoint( const css::awt::Point& aPoint ) override; + virtual OUString SAL_CALL getSelectedText( ) override; + virtual sal_Int32 SAL_CALL getSelectionStart( ) override; + virtual sal_Int32 SAL_CALL getSelectionEnd( ) override; + virtual sal_Bool SAL_CALL setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual OUString SAL_CALL getText( ) override; + virtual OUString SAL_CALL getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual sal_Bool SAL_CALL copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL scrollSubstringTo( sal_Int32 nStartIndex, sal_Int32 nEndIndex, css::accessibility::AccessibleScrollType aScrollType) override; + + // XAccessibleHypertext + virtual sal_Int32 SAL_CALL getHyperLinkCount() override; + virtual css::uno::Reference< css::accessibility::XAccessibleHyperlink > + SAL_CALL getHyperLink( sal_Int32 nLinkIndex ) override; + virtual sal_Int32 SAL_CALL getHyperLinkIndex( sal_Int32 nCharIndex ) override; + + SwAccessibleMap *GetAccessibleMap(){ return GetMap();} + +public: + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet> SAL_CALL + getAccessibleRelationSet() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accnotexthyperlink.cxx b/sw/source/core/access/accnotexthyperlink.cxx new file mode 100644 index 000000000..36ded7390 --- /dev/null +++ b/sw/source/core/access/accnotexthyperlink.cxx @@ -0,0 +1,234 @@ +/* -*- 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 . + */ + +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +#include <comphelper/accessiblekeybindinghelper.hxx> +#include <swurl.hxx> +#include <vcl/svapp.hxx> +#include <frmfmt.hxx> + +#include "accnotexthyperlink.hxx" + +#include <fmturl.hxx> + +#include <vcl/imap.hxx> +#include <vcl/imapobj.hxx> +#include <vcl/keycodes.hxx> + +#include <accmap.hxx> + +using namespace css; +using namespace css::lang; +using namespace css::uno; +using namespace css::accessibility; + +SwAccessibleNoTextHyperlink::SwAccessibleNoTextHyperlink( SwAccessibleNoTextFrame *p, const SwFrame *aFrame ) : + mxFrame( p ), + mpFrame( aFrame ) +{ +} + +// XAccessibleAction +sal_Int32 SAL_CALL SwAccessibleNoTextHyperlink::getAccessibleActionCount() +{ + SolarMutexGuard g; + + SwFormatURL aURL( GetFormat()->GetURL() ); + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + return pMap->GetIMapObjectCount(); + } + else if( !aURL.GetURL().isEmpty() ) + { + return 1; + } + + return 0; +} + +sal_Bool SAL_CALL SwAccessibleNoTextHyperlink::doAccessibleAction( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + bool bRet = false; + SwFormatURL aURL( GetFormat()->GetURL() ); + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + IMapObject* pMapObj = pMap->GetIMapObject(nIndex); + if (!pMapObj->GetURL().isEmpty()) + { + SwViewShell *pVSh = mxFrame->GetShell(); + if( pVSh ) + { + LoadURL( *pVSh, pMapObj->GetURL(), LoadUrlFlags::NONE, + pMapObj->GetTarget() ); + bRet = true; + } + } + } + else if (!aURL.GetURL().isEmpty()) + { + SwViewShell *pVSh = mxFrame->GetShell(); + if( pVSh ) + { + LoadURL( *pVSh, aURL.GetURL(), LoadUrlFlags::NONE, + aURL.GetTargetFrameName() ); + bRet = true; + } + } + + return bRet; +} + +OUString SAL_CALL SwAccessibleNoTextHyperlink::getAccessibleActionDescription( + sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + OUString sDesc; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + SwFormatURL aURL( GetFormat()->GetURL() ); + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + IMapObject* pMapObj = pMap->GetIMapObject(nIndex); + if (!pMapObj->GetDesc().isEmpty()) + sDesc = pMapObj->GetDesc(); + else if (!pMapObj->GetURL().isEmpty()) + sDesc = pMapObj->GetURL(); + } + else if( !aURL.GetURL().isEmpty() ) + sDesc = aURL.GetName(); + + return sDesc; +} + +Reference< XAccessibleKeyBinding > SAL_CALL + SwAccessibleNoTextHyperlink::getAccessibleActionKeyBinding( sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + Reference< XAccessibleKeyBinding > xKeyBinding; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + bool bIsValid = false; + SwFormatURL aURL( GetFormat()->GetURL() ); + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + IMapObject* pMapObj = pMap->GetIMapObject(nIndex); + if (!pMapObj->GetURL().isEmpty()) + bIsValid = true; + } + else if (!aURL.GetURL().isEmpty()) + bIsValid = true; + + if(bIsValid) + { + rtl::Reference<::comphelper::OAccessibleKeyBindingHelper> pKeyBindingHelper = + new ::comphelper::OAccessibleKeyBindingHelper(); + xKeyBinding = pKeyBindingHelper; + + css::awt::KeyStroke aKeyStroke; + aKeyStroke.Modifiers = 0; + aKeyStroke.KeyCode = KEY_RETURN; + aKeyStroke.KeyChar = 0; + aKeyStroke.KeyFunc = 0; + pKeyBindingHelper->AddKeyBinding( aKeyStroke ); + } + + return xKeyBinding; +} + +// XAccessibleHyperlink +Any SAL_CALL SwAccessibleNoTextHyperlink::getAccessibleActionAnchor( + sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + Any aRet; + //SwFrame* pAnchor = static_cast<SwFlyFrame*>(mpFrame)->GetAnchor(); + Reference< XAccessible > xAnchor = mxFrame->GetAccessibleMap()->GetContext(mpFrame); + //SwAccessibleNoTextFrame* pFrame = xFrame.get(); + //Reference< XAccessible > xAnchor = (XAccessible*)pFrame; + aRet <<= xAnchor; + return aRet; +} + +Any SAL_CALL SwAccessibleNoTextHyperlink::getAccessibleActionObject( + sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + SwFormatURL aURL( GetFormat()->GetURL() ); + OUString retText; + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + IMapObject* pMapObj = pMap->GetIMapObject(nIndex); + if (!pMapObj->GetURL().isEmpty()) + retText = pMapObj->GetURL(); + } + else if ( !aURL.GetURL().isEmpty() ) + retText = aURL.GetURL(); + + Any aRet; + aRet <<= retText; + return aRet; +} + +sal_Int32 SAL_CALL SwAccessibleNoTextHyperlink::getStartIndex() +{ + return 0; +} + +sal_Int32 SAL_CALL SwAccessibleNoTextHyperlink::getEndIndex() +{ + return 0; +} + +sal_Bool SAL_CALL SwAccessibleNoTextHyperlink::isValid( ) +{ + SolarMutexGuard g; + + SwFormatURL aURL( GetFormat()->GetURL() ); + + if( aURL.GetMap() || !aURL.GetURL().isEmpty() ) + return true; + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accnotexthyperlink.hxx b/sw/source/core/access/accnotexthyperlink.hxx new file mode 100644 index 000000000..b986abc87 --- /dev/null +++ b/sw/source/core/access/accnotexthyperlink.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCNOTEXTHYPERLINK_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCNOTEXTHYPERLINK_HXX + +#include <com/sun/star/accessibility/XAccessibleHyperlink.hpp> +#include <cppuhelper/implbase.hxx> +#include <layfrm.hxx> + +#include "accnotextframe.hxx" + +class SwAccessibleNoTextHyperlink : + public ::cppu::WeakImplHelper< + css::accessibility::XAccessibleHyperlink > +{ + friend class SwAccessibleNoTextFrame; + + ::rtl::Reference< SwAccessibleNoTextFrame > mxFrame; + const SwFrame *mpFrame; + + SwFrameFormat *GetFormat() + { + return const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(mpFrame))->GetFormat(); + } +public: + + SwAccessibleNoTextHyperlink( SwAccessibleNoTextFrame *p, const SwFrame* aFrame ); + + // XAccessibleAction + virtual sal_Int32 SAL_CALL getAccessibleActionCount() override; + virtual sal_Bool SAL_CALL doAccessibleAction( sal_Int32 nIndex ) override; + virtual OUString SAL_CALL getAccessibleActionDescription( + sal_Int32 nIndex ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleKeyBinding > SAL_CALL + getAccessibleActionKeyBinding( sal_Int32 nIndex ) override; + + // XAccessibleHyperlink + virtual css::uno::Any SAL_CALL getAccessibleActionAnchor( + sal_Int32 nIndex ) override; + virtual css::uno::Any SAL_CALL getAccessibleActionObject( + sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getStartIndex() override; + virtual sal_Int32 SAL_CALL getEndIndex() override; + virtual sal_Bool SAL_CALL isValid( ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpage.cxx b/sw/source/core/access/accpage.cxx new file mode 100644 index 000000000..a22b01970 --- /dev/null +++ b/sw/source/core/access/accpage.cxx @@ -0,0 +1,160 @@ +/* -*- 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 . + */ + +#include <vcl/window.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <cppuhelper/supportsservice.hxx> +#include "accpage.hxx" + +#include <strings.hrc> +#include <pagefrm.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +using uno::Sequence; + +constexpr OUStringLiteral sImplementationName = u"com.sun.star.comp.Writer.SwAccessiblePageView"; + +bool SwAccessiblePage::IsSelected() +{ + return GetMap()->IsPageSelected( static_cast < const SwPageFrame * >( GetFrame() ) ); +} + +void SwAccessiblePage::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + // FOCUSABLE + rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + + // FOCUSED + if( IsSelected() ) + { + OSL_ENSURE( m_bIsSelected, "bSelected out of sync" ); + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + } +} + +void SwAccessiblePage::InvalidateCursorPos_() +{ + bool bNewSelected = IsSelected(); + bool bOldSelected; + + { + std::scoped_lock aGuard( m_Mutex ); + bOldSelected = m_bIsSelected; + m_bIsSelected = bNewSelected; + } + + if( bNewSelected ) + { + // remember that object as the one that has the caret. This is + // necessary to notify that object if the cursor leaves it. + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + if( bOldSelected != bNewSelected ) + { + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, bNewSelected ); + } +} + +void SwAccessiblePage::InvalidateFocus_() +{ + vcl::Window *pWin = GetWindow(); + if( !pWin ) + return; + + bool bSelected; + + { + std::scoped_lock aGuard( m_Mutex ); + bSelected = m_bIsSelected; + } + OSL_ENSURE( bSelected, "focus object should be selected" ); + + FireStateChangedEvent( AccessibleStateType::FOCUSED, + pWin->HasFocus() && bSelected ); +} + +SwAccessiblePage::SwAccessiblePage(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFrame* pFrame ) + : SwAccessibleContext( pInitMap, AccessibleRole::PANEL, pFrame ) + , m_bIsSelected( false ) +{ + assert(pFrame != nullptr); + assert(pInitMap != nullptr); + assert(pFrame->IsPageFrame()); + + OUString sPage = OUString::number( + static_cast<const SwPageFrame*>( GetFrame() )->GetPhyPageNum() ); + SetName( GetResource( STR_ACCESS_PAGE_NAME, &sPage ) ); +} + +SwAccessiblePage::~SwAccessiblePage() +{ +} + +bool SwAccessiblePage::HasCursor() +{ + std::scoped_lock aGuard( m_Mutex ); + return m_bIsSelected; +} + +OUString SwAccessiblePage::getImplementationName( ) +{ + return sImplementationName; +} + +sal_Bool SwAccessiblePage::supportsService( const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence<OUString> SwAccessiblePage::getSupportedServiceNames( ) +{ + return { "com.sun.star.text.AccessiblePageView", sAccessibleServiceName }; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessiblePage::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +OUString SwAccessiblePage::getAccessibleDescription( ) +{ + ThrowIfDisposed(); + + OUString sArg( GetFormattedPageNumber() ); + return GetResource( STR_ACCESS_PAGE_DESC, &sArg ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpage.hxx b/sw/source/core/access/accpage.hxx new file mode 100644 index 000000000..006eda746 --- /dev/null +++ b/sw/source/core/access/accpage.hxx @@ -0,0 +1,77 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPAGE_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPAGE_HXX + +#include "acccontext.hxx" + +/** + * accessibility implementation for the page (SwPageFrame) + * The page is _only_ visible in the page preview. For the regular + * document view, it doesn't make sense to add this additional element + * into the hierarchy. For the page preview, however, the page is the + * important. + */ +class SwAccessiblePage : public SwAccessibleContext +{ + bool m_bIsSelected; // protected by base class mutex + + bool IsSelected(); + + using SwAccessibleFrame::GetBounds; + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets + // FOCUSABLE(1) and FOCUSED(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual void InvalidateCursorPos_() override; + virtual void InvalidateFocus_() override; + + virtual ~SwAccessiblePage() override; + +public: + // convenience constructor to avoid typecast; + // may only be called with SwPageFrame argument + SwAccessiblePage(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFrame* pFrame); + + // XAccessibleContext methods that need to be overridden + + virtual OUString SAL_CALL getAccessibleDescription() override; + + // XServiceInfo + + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService ( + const OUString& sServiceName) override; + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + virtual bool HasCursor() override; // required by map to remember that object +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpara.cxx b/sw/source/core/access/accpara.cxx new file mode 100644 index 000000000..f3b0d9070 --- /dev/null +++ b/sw/source/core/access/accpara.cxx @@ -0,0 +1,3546 @@ +/* -*- 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 . + */ + +#include <memory> +#include <numeric> +#include <txtfrm.hxx> +#include <flyfrm.hxx> +#include <mdiexp.hxx> +#include <ndtxt.hxx> +#include <pam.hxx> +#include <unotextrange.hxx> +#include <unocrsrhelper.hxx> +#include <crstate.hxx> +#include <accmap.hxx> +#include <fesh.hxx> +#include <viewopt.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <sal/log.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleScrollType.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <unotools/accessiblestatesethelper.hxx> +#include <com/sun/star/i18n/Boundary.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <breakit.hxx> +#include "accpara.hxx" +#include "accportions.hxx" +#include <sfx2/viewsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/dispatch.hxx> +#include <unocrsr.hxx> +#include <unoport.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include "acchyperlink.hxx" +#include "acchypertextdata.hxx" +#include <unotools/accessiblerelationsethelper.hxx> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <comphelper/accessibletexthelper.hxx> +#include <algorithm> +#include <docufld.hxx> +#include <txtfld.hxx> +#include <fmtfld.hxx> +#include <modcfg.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <swmodule.hxx> +#include <redline.hxx> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/awt/FontStrikeout.hpp> +#include <com/sun/star/awt/FontSlant.hpp> +#include <wrong.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <swatrset.hxx> +#include <unosett.hxx> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <com/sun/star/text/WritingMode2.hpp> +#include <viewimp.hxx> +#include "textmarkuphelper.hxx" +#include "parachangetrackinginfo.hxx" +#include <com/sun/star/text/TextMarkupType.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <svx/colorwindow.hxx> +#include <o3tl/string_view.hxx> +#include <editeng/editids.hrc> + +#include <reffld.hxx> +#include <flddat.hxx> +#include "../../uibase/inc/fldmgr.hxx" +#include <fldbas.hxx> // SwField + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::container; + +using beans::PropertyValue; +using beans::XMultiPropertySet; +using beans::UnknownPropertyException; +using beans::PropertyState_DIRECT_VALUE; + +using std::max; +using std::min; +using std::sort; + +namespace com::sun::star::text { + class XText; +} + +constexpr OUStringLiteral sServiceName = u"com.sun.star.text.AccessibleParagraphView"; +constexpr OUStringLiteral sImplementationName = u"com.sun.star.comp.Writer.SwAccessibleParagraphView"; + +OUString const & SwAccessibleParagraph::GetString() +{ + return GetPortionData().GetAccessibleString(); +} + +OUString SwAccessibleParagraph::GetDescription() +{ + return OUString(); // provide empty description for paragraphs +} + +sal_Int32 SwAccessibleParagraph::GetCaretPos() +{ + sal_Int32 nRet = -1; + + // get the selection's point, and test whether it's in our node + // #i27301# - consider adjusted method signature + SwPaM* pCaret = GetCursor( false ); // caret is first PaM in PaM-ring + + if( pCaret != nullptr ) + { + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(GetFrame())); + assert(pTextFrame); + + // check whether the point points into 'our' node + SwPosition* pPoint = pCaret->GetPoint(); + if (sw::FrameContainsNode(*pTextFrame, pPoint->nNode.GetIndex())) + { + // same node? Then check whether it's also within 'our' part + // of the paragraph + const TextFrameIndex nIndex = pTextFrame->MapModelToViewPos(*pPoint); + if(!GetPortionData().IsValidCorePosition( nIndex ) || + (GetPortionData().IsZeroCorePositionData() + && nIndex == TextFrameIndex(0))) + { + bool bFormat = pTextFrame->HasPara(); + if(bFormat) + { + ClearPortionData(); + UpdatePortionData(); + } + } + if( GetPortionData().IsValidCorePosition( nIndex ) ) + { + // Yes, it's us! + // consider that cursor/caret is in front of the list label + if ( pCaret->IsInFrontOfLabel() ) + { + nRet = 0; + } + else + { + nRet = GetPortionData().GetAccessiblePosition( nIndex ); + } + + OSL_ENSURE( nRet >= 0, "invalid cursor?" ); + OSL_ENSURE( nRet <= GetPortionData().GetAccessibleString(). + getLength(), "invalid cursor?" ); + } + // else: in this paragraph, but in different frame + } + // else: not in this paragraph + } + // else: no cursor -> no caret + + return nRet; +} + +// #i27301# - new parameter <_bForSelection> +SwPaM* SwAccessibleParagraph::GetCursor( const bool _bForSelection ) +{ + // get the cursor shell; if we don't have any, we don't have a + // cursor/selection either + SwPaM* pCursor = nullptr; + SwCursorShell* pCursorShell = SwAccessibleParagraph::GetCursorShell(); + // #i27301# - if cursor is retrieved for selection, the cursors for + // a table selection has to be returned. + if ( pCursorShell != nullptr && + ( _bForSelection || !pCursorShell->IsTableMode() ) ) + { + SwFEShell *pFESh = dynamic_cast<SwFEShell*>(pCursorShell); + if( !pFESh || + !(pFESh->IsFrameSelected() || pFESh->IsObjSelected() > 0) ) + { + // get the selection, and test whether it affects our text node + pCursor = pCursorShell->GetCursor( false /* ??? */ ); + } + } + + return pCursor; +} + +bool SwAccessibleParagraph::IsHeading() const +{ + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + const SwTextNode *pTextNd = pFrame->GetTextNodeForParaProps(); + return pTextNd->IsOutline(); +} + +void SwAccessibleParagraph::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + // MULTILINE + rStateSet.AddState( AccessibleStateType::MULTI_LINE ); + + if (GetCursorShell()) + { + // MULTISELECTABLE + rStateSet.AddState(AccessibleStateType::MULTI_SELECTABLE); + // FOCUSABLE + rStateSet.AddState(AccessibleStateType::FOCUSABLE); + } + + // FOCUSED (simulates node index of cursor) + SwPaM* pCaret = GetCursor( false ); // #i27301# - consider adjusted method signature + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + assert(pFrame); + if (pCaret != nullptr && + sw::FrameContainsNode(*pFrame, pCaret->GetPoint()->nNode.GetIndex()) && + m_nOldCaretPos != -1) + { + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } +} + +void SwAccessibleParagraph::InvalidateContent_( bool bVisibleDataFired ) +{ + OUString sOldText( GetString() ); + + ClearPortionData(); + + const OUString& rText = GetString(); + + if( rText != sOldText ) + { + // The text is changed + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TEXT_CHANGED; + + // determine exact changes between sOldText and rText + (void)comphelper::OCommonAccessibleText::implInitTextChangedEvent(sOldText, rText, + aEvent.OldValue, + aEvent.NewValue); + + FireAccessibleEvent( aEvent ); + uno::Reference< XAccessible > xparent = getAccessibleParent(); + uno::Reference< XAccessibleContext > xAccContext(xparent,uno::UNO_QUERY); + if (xAccContext.is() && xAccContext->getAccessibleRole() == AccessibleRole::TABLE_CELL) + { + SwAccessibleContext* pPara = static_cast< SwAccessibleContext* >(xparent.get()); + if(pPara) + { + AccessibleEventObject aParaEvent; + aParaEvent.EventId = AccessibleEventId::VALUE_CHANGED; + pPara->FireAccessibleEvent(aParaEvent); + } + } + } + else if( !bVisibleDataFired ) + { + FireVisibleDataEvent(); + } + + bool bNewIsHeading = IsHeading(); + //Get the real heading level, Heading1 ~ Heading10 + m_nHeadingLevel = GetRealHeadingLevel(); + bool bOldIsHeading; + { + std::scoped_lock aGuard( m_Mutex ); + bOldIsHeading = m_bIsHeading; + if( m_bIsHeading != bNewIsHeading ) + m_bIsHeading = bNewIsHeading; + } + + if( bNewIsHeading != bOldIsHeading ) + { + // The role has changed + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::ROLE_CHANGED; + + FireAccessibleEvent( aEvent ); + } + + if( rText == sOldText ) + return; + + OUString sNewDesc( GetDescription() ); + OUString sOldDesc; + { + std::scoped_lock aGuard( m_Mutex ); + sOldDesc = m_sDesc; + if( m_sDesc != sNewDesc ) + m_sDesc = sNewDesc; + } + + if( sNewDesc != sOldDesc ) + { + // The text is changed + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED; + aEvent.OldValue <<= sOldDesc; + aEvent.NewValue <<= sNewDesc; + + FireAccessibleEvent( aEvent ); + } +} + +void SwAccessibleParagraph::InvalidateCursorPos_() +{ + // The text is changed + sal_Int32 nNew = GetCaretPos(); + sal_Int32 nOld; + { + std::scoped_lock aGuard( m_Mutex ); + nOld = m_nOldCaretPos; + m_nOldCaretPos = nNew; + } + if( -1 != nNew ) + { + // remember that object as the one that has the caret. This is + // necessary to notify that object if the cursor leaves it. + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + vcl::Window *pWin = GetWindow(); + if( nOld == nNew ) + return; + + // The cursor's node position is simulated by the focus! + if( pWin && pWin->HasFocus() && -1 == nOld ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, true ); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CARET_CHANGED; + aEvent.OldValue <<= nOld; + aEvent.NewValue <<= nNew; + + FireAccessibleEvent( aEvent ); + + if( pWin && pWin->HasFocus() && -1 == nNew ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, false ); + //To send TEXT_SELECTION_CHANGED event + sal_Int32 nStart=0; + sal_Int32 nEnd =0; + bool bCurSelection = GetSelection(nStart,nEnd); + if(m_bLastHasSelection || bCurSelection ) + { + aEvent.EventId = AccessibleEventId::TEXT_SELECTION_CHANGED; + aEvent.OldValue.clear(); + aEvent.NewValue.clear(); + FireAccessibleEvent(aEvent); + } + m_bLastHasSelection =bCurSelection; + +} + +void SwAccessibleParagraph::InvalidateFocus_() +{ + vcl::Window *pWin = GetWindow(); + if( pWin ) + { + sal_Int32 nPos; + { + std::scoped_lock aGuard( m_Mutex ); + nPos = m_nOldCaretPos; + } + OSL_ENSURE( nPos != -1, "focus object should be selected" ); + + FireStateChangedEvent( AccessibleStateType::FOCUSED, + pWin->HasFocus() && nPos != -1 ); + } +} + +SwAccessibleParagraph::SwAccessibleParagraph( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwTextFrame& rTextFrame ) + : SwAccessibleContext( pInitMap, AccessibleRole::PARAGRAPH, &rTextFrame ) + , m_nOldCaretPos( -1 ) + , m_bIsHeading( false ) + //Get the real heading level, Heading1 ~ Heading10 + , m_nHeadingLevel (-1) + , m_aSelectionHelper( *this ) + , mpParaChangeTrackInfo( new SwParaChangeTrackingInfo( rTextFrame ) ) // #i108125# + , m_bLastHasSelection(false) //To add TEXT_SELECTION_CHANGED event +{ + StartListening(const_cast<SwTextFrame&>(rTextFrame)); + m_bIsHeading = IsHeading(); + //Get the real heading level, Heading1 ~ Heading10 + m_nHeadingLevel = GetRealHeadingLevel(); + SetName( OUString() ); // set an empty accessibility name for paragraphs +} + +SwAccessibleParagraph::~SwAccessibleParagraph() +{ + SolarMutexGuard aGuard; + + m_pPortionData.reset(); + m_pHyperTextData.reset(); + mpParaChangeTrackInfo.reset(); // #i108125# + EndListeningAll(); +} + +bool SwAccessibleParagraph::HasCursor() +{ + std::scoped_lock aGuard( m_Mutex ); + return m_nOldCaretPos != -1; +} + +void SwAccessibleParagraph::UpdatePortionData() +{ + // obtain the text frame + const SwTextFrame* pFrame = static_cast<const SwTextFrame*>( GetFrame() ); + OSL_ENSURE( pFrame != nullptr, "The text frame has vanished!" ); + if (!pFrame) + ClearPortionData(); + else + { + OSL_ENSURE( pFrame->IsTextFrame(), "The text frame has mutated!" ); + // build new portion data + m_pPortionData.reset( new SwAccessiblePortionData( + pFrame, GetMap()->GetShell()->GetViewOptions()) ); + pFrame->VisitPortions( *m_pPortionData ); + } + OSL_ENSURE( m_pPortionData != nullptr, "UpdatePortionData() failed" ); +} + +void SwAccessibleParagraph::ClearPortionData() +{ + m_pPortionData.reset(); + m_pHyperTextData.reset(); +} + +void SwAccessibleParagraph::ExecuteAtViewShell( sal_uInt16 nSlot ) +{ + OSL_ENSURE( GetMap() != nullptr, "no map?" ); + SwViewShell* pViewShell = GetMap()->GetShell(); + + OSL_ENSURE( pViewShell != nullptr, "View shell expected!" ); + SfxViewShell* pSfxShell = pViewShell->GetSfxViewShell(); + + OSL_ENSURE( pSfxShell != nullptr, "SfxViewShell shell expected!" ); + if( !pSfxShell ) + return; + + SfxViewFrame *pFrame = pSfxShell->GetViewFrame(); + OSL_ENSURE( pFrame != nullptr, "View frame expected!" ); + if( !pFrame ) + return; + + SfxDispatcher *pDispatcher = pFrame->GetDispatcher(); + OSL_ENSURE( pDispatcher != nullptr, "Dispatcher expected!" ); + if( !pDispatcher ) + return; + + pDispatcher->Execute( nSlot ); +} + +rtl::Reference<SwXTextPortion> SwAccessibleParagraph::CreateUnoPortion( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) +{ + OSL_ENSURE( (IsValidChar(nStartIndex, GetString().getLength()) && + (nEndIndex == -1)) || + IsValidRange(nStartIndex, nEndIndex, GetString().getLength()), + "please check parameters before calling this method" ); + + const TextFrameIndex nStart = GetPortionData().GetCoreViewPosition(nStartIndex); + const TextFrameIndex nEnd = (nEndIndex == -1) + ? (nStart + TextFrameIndex(1)) + : GetPortionData().GetCoreViewPosition(nEndIndex); + + // create UNO cursor + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + SwPosition aStartPos(pFrame->MapViewToModelPos(nStart)); + auto pUnoCursor(const_cast<SwDoc&>(pFrame->GetDoc()).CreateUnoCursor(aStartPos)); + pUnoCursor->SetMark(); + *pUnoCursor->GetMark() = pFrame->MapViewToModelPos(nEnd); + + // create a (dummy) text portion to be returned + uno::Reference<text::XText> aEmpty; + return new SwXTextPortion ( pUnoCursor.get(), aEmpty, PORTION_TEXT); +} + +// range checking for parameter + +bool SwAccessibleParagraph::IsValidChar( + sal_Int32 nPos, sal_Int32 nLength) +{ + return (nPos >= 0) && (nPos < nLength); +} + +bool SwAccessibleParagraph::IsValidPosition( + sal_Int32 nPos, sal_Int32 nLength) +{ + return (nPos >= 0) && (nPos <= nLength); +} + +bool SwAccessibleParagraph::IsValidRange( + sal_Int32 nBegin, sal_Int32 nEnd, sal_Int32 nLength) +{ + return IsValidPosition(nBegin, nLength) && IsValidPosition(nEnd, nLength); +} + +//the function is to check whether the position is in a redline range. +const SwRangeRedline* SwAccessibleParagraph::GetRedlineAtIndex() +{ + const SwRangeRedline* pRedline = nullptr; + SwPaM* pCrSr = GetCursor( true ); + if ( pCrSr ) + { + SwPosition* pStart = pCrSr->Start(); + pRedline = pStart->GetDoc().getIDocumentRedlineAccess().GetRedline(*pStart, nullptr); + } + + return pRedline; +} + +// text boundaries + +bool SwAccessibleParagraph::GetCharBoundary( + i18n::Boundary& rBound, + sal_Int32 nPos ) +{ + if( GetPortionData().FillBoundaryIFDateField( rBound, nPos) ) + return true; + + rBound.startPos = nPos; + rBound.endPos = nPos+1; + return true; +} + +bool SwAccessibleParagraph::GetWordBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ) +{ + // now ask the Break-Iterator for the word + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + // get locale for this position + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + const TextFrameIndex nCorePos = GetPortionData().GetCoreViewPosition(nPos); + lang::Locale aLocale = g_pBreakIt->GetLocale(pFrame->GetLangOfChar(nCorePos, 0, true)); + + // which type of word are we interested in? + // (DICTIONARY_WORD includes punctuation, ANY_WORD doesn't.) + const sal_Int16 nWordType = i18n::WordType::ANY_WORD; + + // get word boundary, as the Break-Iterator sees fit. + rBound = g_pBreakIt->GetBreakIter()->getWordBoundary( + rText, nPos, aLocale, nWordType, true ); + + return true; +} + +bool SwAccessibleParagraph::GetSentenceBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ) +{ + const sal_Unicode* pStr = rText.getStr(); + while( nPos < rText.getLength() && pStr[nPos] == u' ' ) + nPos++; + + GetPortionData().GetSentenceBoundary( rBound, nPos ); + return true; +} + +bool SwAccessibleParagraph::GetLineBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ) +{ + if( rText.getLength() == nPos ) + GetPortionData().GetLastLineBoundary( rBound ); + else + GetPortionData().GetLineBoundary( rBound, nPos ); + return true; +} + +bool SwAccessibleParagraph::GetParagraphBoundary( + i18n::Boundary& rBound, + const OUString& rText ) +{ + rBound.startPos = 0; + rBound.endPos = rText.getLength(); + return true; +} + +bool SwAccessibleParagraph::GetAttributeBoundary( + i18n::Boundary& rBound, + sal_Int32 nPos ) +{ + GetPortionData().GetAttributeBoundary( rBound, nPos ); + return true; +} + +bool SwAccessibleParagraph::GetGlyphBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ) +{ + // ask the Break-Iterator for the glyph by moving one cell + // forward, and then one cell back + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + // get locale for this position + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + const TextFrameIndex nCorePos = GetPortionData().GetCoreViewPosition(nPos); + lang::Locale aLocale = g_pBreakIt->GetLocale(pFrame->GetLangOfChar(nCorePos, 0, true)); + + // get word boundary, as the Break-Iterator sees fit. + const sal_Int16 nIterMode = i18n::CharacterIteratorMode::SKIPCELL; + sal_Int32 nDone = 0; + rBound.endPos = g_pBreakIt->GetBreakIter()->nextCharacters( + rText, nPos, aLocale, nIterMode, 1, nDone ); + rBound.startPos = g_pBreakIt->GetBreakIter()->previousCharacters( + rText, rBound.endPos, aLocale, nIterMode, 1, nDone ); + bool bRet = ((rBound.startPos <= nPos) && (nPos <= rBound.endPos)); + OSL_ENSURE( rBound.startPos <= nPos, "start pos too high" ); + OSL_ENSURE( rBound.endPos >= nPos, "end pos too low" ); + + return bRet; +} + +bool SwAccessibleParagraph::GetTextBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos, + sal_Int16 nTextType ) +{ + // error checking + if( !( AccessibleTextType::LINE == nTextType + ? IsValidPosition( nPos, rText.getLength() ) + : IsValidChar( nPos, rText.getLength() ) ) ) + throw lang::IndexOutOfBoundsException(); + + bool bRet; + + switch( nTextType ) + { + case AccessibleTextType::WORD: + bRet = GetWordBoundary(rBound, rText, nPos); + break; + + case AccessibleTextType::SENTENCE: + bRet = GetSentenceBoundary( rBound, rText, nPos ); + break; + + case AccessibleTextType::PARAGRAPH: + bRet = GetParagraphBoundary( rBound, rText ); + break; + + case AccessibleTextType::CHARACTER: + bRet = GetCharBoundary( rBound, nPos ); + break; + + case AccessibleTextType::LINE: + //Solve the problem of returning wrong LINE and PARAGRAPH + if((nPos == rText.getLength()) && nPos > 0) + bRet = GetLineBoundary( rBound, rText, nPos - 1); + else + bRet = GetLineBoundary( rBound, rText, nPos ); + break; + + case AccessibleTextType::ATTRIBUTE_RUN: + bRet = GetAttributeBoundary( rBound, nPos ); + break; + + case AccessibleTextType::GLYPH: + bRet = GetGlyphBoundary( rBound, rText, nPos ); + break; + + default: + throw lang::IllegalArgumentException( ); + } + + return bRet; +} + +OUString SAL_CALL SwAccessibleParagraph::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + std::scoped_lock aGuard2( m_Mutex ); + if( m_sDesc.isEmpty() ) + m_sDesc = GetDescription(); + + return m_sDesc; +} + +lang::Locale SAL_CALL SwAccessibleParagraph::getLocale() +{ + SolarMutexGuard aGuard; + + const SwTextFrame *pTextFrame = GetFrame()->DynCastTextFrame(); + if( !pTextFrame ) + { + throw uno::RuntimeException("no SwTextFrame", static_cast<cppu::OWeakObject*>(this)); + } + + lang::Locale aLoc(g_pBreakIt->GetLocale(pTextFrame->GetLangOfChar(TextFrameIndex(0), 0, true))); + + return aLoc; +} + +// #i27138# - paragraphs are in relation CONTENT_FLOWS_FROM and/or CONTENT_FLOWS_TO +uno::Reference<XAccessibleRelationSet> SAL_CALL SwAccessibleParagraph::getAccessibleRelationSet() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + rtl::Reference<utl::AccessibleRelationSetHelper> pHelper = new utl::AccessibleRelationSetHelper(); + + const SwTextFrame* pTextFrame = GetFrame()->DynCastTextFrame(); + OSL_ENSURE( pTextFrame, + "<SwAccessibleParagraph::getAccessibleRelationSet()> - missing text frame"); + if ( pTextFrame ) + { + const SwContentFrame* pPrevContentFrame( pTextFrame->FindPrevCnt() ); + if ( pPrevContentFrame ) + { + uno::Sequence< uno::Reference<XInterface> > aSequence { GetMap()->GetContext( pPrevContentFrame ) }; + AccessibleRelation aAccRel( AccessibleRelationType::CONTENT_FLOWS_FROM, + aSequence ); + pHelper->AddRelation( aAccRel ); + } + + const SwContentFrame* pNextContentFrame( pTextFrame->FindNextCnt( true ) ); + if ( pNextContentFrame ) + { + uno::Sequence< uno::Reference<XInterface> > aSequence { GetMap()->GetContext( pNextContentFrame ) }; + AccessibleRelation aAccRel( AccessibleRelationType::CONTENT_FLOWS_TO, + aSequence ); + pHelper->AddRelation( aAccRel ); + } + } + + return pHelper; +} + +void SAL_CALL SwAccessibleParagraph::grabFocus() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // get cursor shell + SwCursorShell *pCursorSh = GetCursorShell(); + SwPaM *pCursor = GetCursor( false ); // #i27301# - consider new method signature + const SwTextFrame *pTextFrame = static_cast<const SwTextFrame*>( GetFrame() ); + + if (pCursorSh != nullptr && + ( pCursor == nullptr || + !sw::FrameContainsNode(*pTextFrame, pCursor->GetPoint()->nNode.GetIndex()) || + !pTextFrame->IsInside(pTextFrame->MapModelToViewPos(*pCursor->GetPoint())))) + { + // create pam for selection + SwPosition const aStartPos(pTextFrame->MapViewToModelPos(pTextFrame->GetOffset())); + SwPaM aPaM( aStartPos ); + + // set PaM at cursor shell + Select( aPaM ); + + } + + // ->#i13955# + vcl::Window * pWindow = GetWindow(); + + if (pWindow != nullptr) + pWindow->GrabFocus(); + // <-#i13955# +} + +// #i71385# +static bool lcl_GetBackgroundColor( Color & rColor, + const SwFrame* pFrame, + SwCursorShell* pCursorSh ) +{ + const SvxBrushItem* pBackgroundBrush = nullptr; + std::optional<Color> xSectionTOXColor; + SwRect aDummyRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + if ( pFrame && + pFrame->GetBackgroundBrush( aFillAttributes, pBackgroundBrush, xSectionTOXColor, aDummyRect, false, /*bConsiderTextBox=*/false ) ) + { + if ( xSectionTOXColor ) + { + rColor = *xSectionTOXColor; + return true; + } + else + { + rColor = pBackgroundBrush->GetColor(); + return true; + } + } + else if ( pCursorSh ) + { + rColor = pCursorSh->Imp()->GetRetoucheColor(); + return true; + } + + return false; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getForeground() +{ + SolarMutexGuard g; + + Color aBackgroundCol; + + if ( lcl_GetBackgroundColor( aBackgroundCol, GetFrame(), GetCursorShell() ) ) + { + if ( aBackgroundCol.IsDark() ) + { + return sal_Int32(COL_WHITE); + } + else + { + return sal_Int32(COL_BLACK); + } + } + + return SwAccessibleContext::getForeground(); +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getBackground() +{ + SolarMutexGuard g; + + Color aBackgroundCol; + + if ( lcl_GetBackgroundColor( aBackgroundCol, GetFrame(), GetCursorShell() ) ) + { + return sal_Int32(aBackgroundCol); + } + + return SwAccessibleContext::getBackground(); +} + +OUString SAL_CALL SwAccessibleParagraph::getImplementationName() +{ + return sImplementationName; +} + +sal_Bool SAL_CALL SwAccessibleParagraph::supportsService( + const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleParagraph::getSupportedServiceNames() +{ + return { sServiceName, sAccessibleServiceName }; +} + +static uno::Sequence< OUString > const & getAttributeNames() +{ + static uno::Sequence< OUString > const aNames + { + // Add the font name to attribute list + // sorted list of strings + UNO_NAME_CHAR_BACK_COLOR, + UNO_NAME_CHAR_COLOR, + UNO_NAME_CHAR_CONTOURED, + UNO_NAME_CHAR_EMPHASIS, + UNO_NAME_CHAR_ESCAPEMENT, + UNO_NAME_CHAR_FONT_NAME, + UNO_NAME_CHAR_HEIGHT, + UNO_NAME_CHAR_POSTURE, + UNO_NAME_CHAR_SHADOWED, + UNO_NAME_CHAR_STRIKEOUT, + UNO_NAME_CHAR_UNDERLINE, + UNO_NAME_CHAR_UNDERLINE_COLOR, + UNO_NAME_CHAR_WEIGHT, + }; + return aNames; +} + +static uno::Sequence< OUString > const & getSupplementalAttributeNames() +{ + static uno::Sequence< OUString > const aNames + { + // sorted list of strings + UNO_NAME_NUMBERING_LEVEL, + UNO_NAME_NUMBERING_RULES, + UNO_NAME_PARA_ADJUST, + UNO_NAME_PARA_BOTTOM_MARGIN, + UNO_NAME_PARA_FIRST_LINE_INDENT, + UNO_NAME_PARA_LEFT_MARGIN, + UNO_NAME_PARA_LINE_SPACING, + UNO_NAME_PARA_RIGHT_MARGIN, + UNO_NAME_TABSTOPS, + }; + return aNames; +} + +// XInterface + +uno::Any SwAccessibleParagraph::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet; + if ( rType == cppu::UnoType<XAccessibleText>::get()) + { + uno::Reference<XAccessibleText> aAccText = static_cast<XAccessibleText *>(*this); // resolve ambiguity + aRet <<= aAccText; + } + else if ( rType == cppu::UnoType<XAccessibleEditableText>::get()) + { + uno::Reference<XAccessibleEditableText> aAccEditText = this; + aRet <<= aAccEditText; + } + else if ( rType == cppu::UnoType<XAccessibleSelection>::get()) + { + uno::Reference<XAccessibleSelection> aAccSel = this; + aRet <<= aAccSel; + } + else if ( rType == cppu::UnoType<XAccessibleHypertext>::get()) + { + uno::Reference<XAccessibleHypertext> aAccHyp = this; + aRet <<= aAccHyp; + } + // #i63870# + // add interface com::sun:star:accessibility::XAccessibleTextAttributes + else if ( rType == cppu::UnoType<XAccessibleTextAttributes>::get()) + { + uno::Reference<XAccessibleTextAttributes> aAccTextAttr = this; + aRet <<= aAccTextAttr; + } + // #i89175# + // add interface com::sun:star:accessibility::XAccessibleTextMarkup + else if ( rType == cppu::UnoType<XAccessibleTextMarkup>::get()) + { + uno::Reference<XAccessibleTextMarkup> aAccTextMarkup = this; + aRet <<= aAccTextMarkup; + } + // add interface com::sun:star:accessibility::XAccessibleMultiLineText + else if ( rType == cppu::UnoType<XAccessibleMultiLineText>::get()) + { + uno::Reference<XAccessibleMultiLineText> aAccMultiLineText = this; + aRet <<= aAccMultiLineText; + } + else if ( rType == cppu::UnoType<XAccessibleTextSelection>::get()) + { + uno::Reference< css::accessibility::XAccessibleTextSelection > aTextExtension = this; + aRet <<= aTextExtension; + } + else if ( rType == cppu::UnoType<XAccessibleExtendedAttributes>::get()) + { + uno::Reference<XAccessibleExtendedAttributes> xAttr = this; + aRet <<= xAttr; + } + else + { + aRet = SwAccessibleContext::queryInterface(rType); + } + + return aRet; +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SwAccessibleParagraph::getTypes() +{ + // #i63870# - add type accessibility::XAccessibleTextAttributes + // #i89175# - add type accessibility::XAccessibleTextMarkup and + return cppu::OTypeCollection( + cppu::UnoType<XAccessibleEditableText>::get(), + cppu::UnoType<XAccessibleTextAttributes>::get(), + ::cppu::UnoType<XAccessibleSelection>::get(), + cppu::UnoType<XAccessibleTextMarkup>::get(), + cppu::UnoType<XAccessibleMultiLineText>::get(), + cppu::UnoType<XAccessibleHypertext>::get(), + SwAccessibleContext::getTypes() ).getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleParagraph::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XAccessibleText + +sal_Int32 SwAccessibleParagraph::getCaretPosition() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nRet = GetCaretPos(); + { + std::scoped_lock aOldCaretPosGuard( m_Mutex ); + OSL_ENSURE( nRet == m_nOldCaretPos, "caret pos out of sync" ); + m_nOldCaretPos = nRet; + } + if( -1 != nRet ) + { + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + return nRet; +} + +sal_Bool SAL_CALL SwAccessibleParagraph::setCaretPosition( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking + sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidPosition( nIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + bool bRet = false; + + // get cursor shell + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + // create pam for selection + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + TextFrameIndex const nFrameIndex(GetPortionData().GetCoreViewPosition(nIndex)); + SwPosition aStartPos(pFrame->MapViewToModelPos(nFrameIndex)); + SwPaM aPaM( aStartPos ); + + // set PaM at cursor shell + bRet = Select( aPaM ); + } + + return bRet; +} + +sal_Unicode SwAccessibleParagraph::getCharacter( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + OUString sText( GetString() ); + + // return character (if valid) + if( !IsValidChar(nIndex, sText.getLength() ) ) + throw lang::IndexOutOfBoundsException(); + + return sText[nIndex]; +} + +css::uno::Sequence< css::style::TabStop > SwAccessibleParagraph::GetCurrentTabStop( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + /* #i12332# The position after the string needs special treatment. + IsValidChar -> IsValidPosition + */ + if( ! (IsValidPosition( nIndex, GetString().getLength() ) ) ) + throw lang::IndexOutOfBoundsException(); + + /* #i12332# */ + bool bBehindText = false; + if ( nIndex == GetString().getLength() ) + bBehindText = true; + + // get model position & prepare GetCharRect() arguments + SwCursorMoveState aMoveState; + aMoveState.m_bRealHeight = true; + aMoveState.m_bRealWidth = true; + SwSpecialPos aSpecialPos; + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + + /* #i12332# FillSpecialPos does not accept nIndex == + GetString().getLength(). In that case nPos is set to the + length of the string in the core. This way GetCharRect + returns the rectangle for a cursor at the end of the + paragraph. */ + const TextFrameIndex nPos = bBehindText + ? TextFrameIndex(pFrame->GetText().getLength()) + : GetPortionData().FillSpecialPos(nIndex, aSpecialPos, aMoveState.m_pSpecialPos ); + + // call GetCharRect + SwRect aCoreRect; + SwPosition aPosition(pFrame->MapViewToModelPos(nPos)); + GetFrame()->GetCharRect( aCoreRect, aPosition, &aMoveState ); + + // already get the caret position + css::uno::Sequence< css::style::TabStop > tabs; + const sal_Int32 nStrLen = pFrame->GetText().getLength(); + if( nStrLen > 0 ) + { + SwFrame* pTFrame = const_cast<SwFrame*>(GetFrame()); + tabs = pTFrame->GetTabStopInfo(aCoreRect.Left()); + } + + if( tabs.hasElements() ) + { + // translate core coordinates into accessibility coordinates + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + SwRect aTmpRect(0, 0, tabs[0].Position, 0); + + tools::Rectangle aScreenRect( GetMap()->CoreToPixel( aTmpRect )); + SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root + + Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds ).TopLeft() ); + aScreenRect.Move( -aFramePixPos.X(), -aFramePixPos.Y() ); + + tabs.getArray()[0].Position = aScreenRect.GetWidth(); + } + + return tabs; +} + +namespace { + +struct IndexCompare +{ + const PropertyValue* pValues; + explicit IndexCompare( const PropertyValue* pVals ) : pValues(pVals) {} + bool operator() ( sal_Int32 a, sal_Int32 b ) const + { + return (pValues[a].Name < pValues[b].Name); + } +}; + +} + +OUString SwAccessibleParagraph::GetFieldTypeNameAtIndex(sal_Int32 nIndex) +{ + OUString strTypeName; + SwFieldMgr aMgr; + SwTextField* pTextField = nullptr; + sal_Int32 nFieldIndex = GetPortionData().GetFieldIndex(nIndex); + if (nFieldIndex >= 0) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + sw::MergedAttrIter iter(*pFrame); + while (SwTextAttr const*const pHt = iter.NextAttr()) + { + if ((pHt->Which() == RES_TXTATR_FIELD + || pHt->Which() == RES_TXTATR_ANNOTATION + || pHt->Which() == RES_TXTATR_INPUTFIELD) + && (nFieldIndex-- == 0)) + { + pTextField = const_cast<SwTextField*>( + static_txtattr_cast<SwTextField const*>(pHt)); + break; + } + else if (pHt->Which() == RES_TXTATR_REFMARK + && (nFieldIndex-- == 0)) + { + strTypeName = "set reference"; + } + } + } + if (pTextField) + { + const SwField* pField = pTextField->GetFormatField().GetField(); + if (pField) + { + strTypeName = SwFieldType::GetTypeStr(pField->GetTypeId()); + const SwFieldIds nWhich = pField->GetTyp()->Which(); + OUString sEntry; + sal_uInt32 subType = 0; + switch (nWhich) + { + case SwFieldIds::DocStat: + subType = static_cast<const SwDocStatField*>(pField)->GetSubType(); + break; + case SwFieldIds::GetRef: + { + switch( pField->GetSubType() ) + { + case REF_BOOKMARK: + { + const SwGetRefField* pRefField = dynamic_cast<const SwGetRefField*>(pField); + if ( pRefField && pRefField->IsRefToHeadingCrossRefBookmark() ) + sEntry = "Headings"; + else if ( pRefField && pRefField->IsRefToNumItemCrossRefBookmark() ) + sEntry = "Numbered Paragraphs"; + else + sEntry = "Bookmarks"; + } + break; + case REF_FOOTNOTE: + sEntry = "Footnotes"; + break; + case REF_ENDNOTE: + sEntry = "Endnotes"; + break; + case REF_SETREFATTR: + sEntry = "Insert Reference"; + break; + case REF_SEQUENCEFLD: + sEntry = static_cast<const SwGetRefField*>(pField)->GetSetRefName(); + break; + } + //Get format string + strTypeName = sEntry; + // <pField->GetFormat() >= 0> is always true as <pField->GetFormat()> is unsigned +// if (pField->GetFormat() >= 0) + { + sEntry = aMgr.GetFormatStr( pField->GetTypeId(), pField->GetFormat() ); + if (sEntry.getLength() > 0) + { + strTypeName += "-" + sEntry; + } + } + } + break; + case SwFieldIds::DateTime: + subType = static_cast<const SwDateTimeField*>(pField)->GetSubType(); + break; + case SwFieldIds::JumpEdit: + { + const sal_uInt32 nFormat= pField->GetFormat(); + const sal_uInt16 nSize = aMgr.GetFormatCount(pField->GetTypeId(), false); + if (nFormat < nSize) + { + sEntry = aMgr.GetFormatStr(pField->GetTypeId(), nFormat); + if (sEntry.getLength() > 0) + { + strTypeName += "-" + sEntry; + } + } + } + break; + case SwFieldIds::ExtUser: + subType = static_cast<const SwExtUserField*>(pField)->GetSubType(); + break; + case SwFieldIds::HiddenText: + case SwFieldIds::SetExp: + { + sEntry = pField->GetTyp()->GetName(); + if (sEntry.getLength() > 0) + { + strTypeName += "-" + sEntry; + } + } + break; + case SwFieldIds::DocInfo: + subType = pField->GetSubType(); + subType &= 0x00ff; + break; + case SwFieldIds::RefPageSet: + { + const SwRefPageSetField* pRPld = static_cast<const SwRefPageSetField*>(pField); + bool bOn = pRPld->IsOn(); + strTypeName += "-"; + if (bOn) + strTypeName += "on"; + else + strTypeName += "off"; + } + break; + case SwFieldIds::Author: + { + strTypeName += "-" + aMgr.GetFormatStr(pField->GetTypeId(), pField->GetFormat() & 0xff); + } + break; + default: break; + } + if (subType > 0 || nWhich == SwFieldIds::DocInfo || nWhich == SwFieldIds::ExtUser || nWhich == SwFieldIds::DocStat) + { + std::vector<OUString> aLst; + aMgr.GetSubTypes(pField->GetTypeId(), aLst); + if (subType < aLst.size()) + sEntry = aLst[subType]; + if (sEntry.getLength() > 0) + { + if (nWhich == SwFieldIds::DocInfo) + { + strTypeName = sEntry; + sal_uInt16 nSize = aMgr.GetFormatCount(pField->GetTypeId(), false); + const sal_uInt16 nExSub = pField->GetSubType() & 0xff00; + if (nSize > 0 && nExSub > 0) + { + //Get extra subtype string + strTypeName += "-"; + sEntry = aMgr.GetFormatStr(pField->GetTypeId(), nExSub/0x0100-1); + strTypeName += sEntry; + } + } + else + { + strTypeName += "-" + sEntry; + } + } + } + } + } + return strTypeName; +} + +// #i63870# - re-implement method on behalf of methods +// <_getDefaultAttributesImpl(..)> and <_getRunAttributesImpl(..)> +uno::Sequence<PropertyValue> SwAccessibleParagraph::getCharacterAttributes( + sal_Int32 nIndex, + const uno::Sequence< OUString >& aRequestedAttributes ) +{ + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const OUString& rText = GetString(); + + if (!IsValidPosition(nIndex, rText.getLength())) + throw lang::IndexOutOfBoundsException(); + + bool bSupplementalMode = false; + uno::Sequence< OUString > aNames = aRequestedAttributes; + if (!aNames.hasElements()) + { + bSupplementalMode = true; + aNames = getAttributeNames(); + } + // retrieve default character attributes + tAccParaPropValMap aDefAttrSeq; + _getDefaultAttributesImpl( aNames, aDefAttrSeq, true ); + + // retrieved run character attributes + tAccParaPropValMap aRunAttrSeq; + _getRunAttributesImpl( nIndex, aNames, aRunAttrSeq ); + + // merge default and run attributes + std::vector< PropertyValue > aValues( aDefAttrSeq.size() ); + sal_Int32 i = 0; + for ( const auto& rDefEntry : aDefAttrSeq ) + { + tAccParaPropValMap::const_iterator aRunIter = + aRunAttrSeq.find( rDefEntry.first ); + if ( aRunIter != aRunAttrSeq.end() ) + { + aValues[i] = aRunIter->second; + } + else + { + aValues[i] = rDefEntry.second; + } + ++i; + } + if( bSupplementalMode ) + { + uno::Sequence< OUString > aSupplementalNames = aRequestedAttributes; + if (!aSupplementalNames.hasElements()) + aSupplementalNames = getSupplementalAttributeNames(); + + tAccParaPropValMap aSupplementalAttrSeq; + _getSupplementalAttributesImpl( aSupplementalNames, aSupplementalAttrSeq ); + + aValues.resize( aValues.size() + aSupplementalAttrSeq.size() ); + + for ( const auto& rSupplementalEntry : aSupplementalAttrSeq ) + { + aValues[i] = rSupplementalEntry.second; + ++i; + } + + _correctValues( nIndex, aValues ); + + aValues.emplace_back(); + + OUString strTypeName = GetFieldTypeNameAtIndex(nIndex); + if (!strTypeName.isEmpty()) + { + aValues.emplace_back(); + PropertyValue& rValueFT = aValues.back(); + rValueFT.Name = "FieldType"; + rValueFT.Value <<= strTypeName.toAsciiLowerCase(); + rValueFT.Handle = -1; + rValueFT.State = PropertyState_DIRECT_VALUE; + } + + //sort property values + // build sorted index array + sal_Int32 nLength = aValues.size(); + std::vector<sal_Int32> aIndices; + aIndices.reserve(nLength); + for (i = 0; i < nLength; ++i) + aIndices.push_back(i); + std::sort(aIndices.begin(), aIndices.end(), IndexCompare(aValues.data())); + // create sorted sequences according to index array + uno::Sequence<PropertyValue> aNewValues( nLength ); + PropertyValue* pNewValues = aNewValues.getArray(); + for (i = 0; i < nLength; ++i) + { + pNewValues[i] = aValues[aIndices[i]]; + } + return aNewValues; + } + + return comphelper::containerToSequence(aValues); +} + +static void SetPutRecursive(SfxItemSet &targetSet, const SfxItemSet &sourceSet) +{ + const SfxItemSet *const pParentSet = sourceSet.GetParent(); + if (pParentSet) + SetPutRecursive(targetSet, *pParentSet); + targetSet.Put(sourceSet); +} + +// #i63870# +void SwAccessibleParagraph::_getDefaultAttributesImpl( + const uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rDefAttrSeq, + const bool bOnlyCharAttrs ) +{ + // retrieve default attributes + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + const SwTextNode *const pTextNode(pFrame->GetTextNodeForParaProps()); + std::optional<SfxItemSet> pSet; + if ( !bOnlyCharAttrs ) + { + pSet.emplace( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()), + svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END - 1, + RES_PARATR_BEGIN, RES_PARATR_END - 1, + RES_FRMATR_BEGIN, RES_FRMATR_END - 1> ); + } + else + { + pSet.emplace( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()), + svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END - 1> ); + } + // #i82637# - From the perspective of the a11y API the default character + // attributes are the character attributes, which are set at the paragraph style + // of the paragraph. The character attributes set at the automatic paragraph + // style of the paragraph are treated as run attributes. + // pTextNode->SwContentNode::GetAttr( *pSet ); + // get default paragraph attributes, if needed, and merge these into <pSet> + if ( !bOnlyCharAttrs ) + { + SfxItemSetFixed<RES_PARATR_BEGIN, RES_PARATR_END - 1, + RES_FRMATR_BEGIN, RES_FRMATR_END - 1> + aParaSet( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()) ); + pTextNode->SwContentNode::GetAttr( aParaSet ); + pSet->Put( aParaSet ); + } + // get default character attributes and merge these into <pSet> + OSL_ENSURE( pTextNode->GetTextColl(), + "<SwAccessibleParagraph::_getDefaultAttributesImpl(..)> - missing paragraph style. Serious defect!" ); + if ( pTextNode->GetTextColl() ) + { + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END - 1> + aCharSet( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()) ); + SetPutRecursive( aCharSet, pTextNode->GetTextColl()->GetAttrSet() ); + pSet->Put( aCharSet ); + } + + // build-up sequence containing the run attributes <rDefAttrSeq> + tAccParaPropValMap aDefAttrSeq; + { + const SfxItemPropertyMap& rPropMap = + aSwMapProvider.GetPropertySet( PROPERTY_MAP_TEXT_CURSOR )->getPropertyMap(); + for ( const auto pEntry : rPropMap.getPropertyEntries() ) + { + const SfxPoolItem* pItem = pSet->GetItem( pEntry->nWID ); + if ( pItem ) + { + uno::Any aVal; + pItem->QueryValue( aVal, pEntry->nMemberId ); + + PropertyValue rPropVal; + rPropVal.Name = pEntry->aName; + rPropVal.Value = aVal; + rPropVal.Handle = -1; + rPropVal.State = beans::PropertyState_DEFAULT_VALUE; + + aDefAttrSeq[rPropVal.Name] = rPropVal; + } + } + + // #i72800# + // add property value entry for the paragraph style + if ( !bOnlyCharAttrs && pTextNode->GetTextColl() ) + { + if ( aDefAttrSeq.find( UNO_NAME_PARA_STYLE_NAME ) == aDefAttrSeq.end() ) + { + PropertyValue rPropVal; + rPropVal.Name = UNO_NAME_PARA_STYLE_NAME; + uno::Any aVal( uno::Any( pTextNode->GetTextColl()->GetName() ) ); + rPropVal.Value = aVal; + rPropVal.Handle = -1; + rPropVal.State = beans::PropertyState_DEFAULT_VALUE; + + aDefAttrSeq[rPropVal.Name] = rPropVal; + } + } + + // #i73371# + // resolve value text::WritingMode2::PAGE of property value entry WritingMode + if ( !bOnlyCharAttrs && GetFrame() ) + { + tAccParaPropValMap::iterator aIter = aDefAttrSeq.find( UNO_NAME_WRITING_MODE ); + if ( aIter != aDefAttrSeq.end() ) + { + PropertyValue rPropVal( aIter->second ); + sal_Int16 nVal = rPropVal.Value.get<sal_Int16>(); + if ( nVal == text::WritingMode2::PAGE ) + { + const SwFrame* pUpperFrame( GetFrame()->GetUpper() ); + while ( pUpperFrame ) + { + if ( pUpperFrame->GetType() & + ( SwFrameType::Page | SwFrameType::Fly | SwFrameType::Section | SwFrameType::Tab | SwFrameType::Cell ) ) + { + if ( pUpperFrame->IsVertical() ) + { + nVal = text::WritingMode2::TB_RL; + } + else if ( pUpperFrame->IsRightToLeft() ) + { + nVal = text::WritingMode2::RL_TB; + } + else + { + nVal = text::WritingMode2::LR_TB; + } + rPropVal.Value <<= nVal; + aDefAttrSeq[rPropVal.Name] = rPropVal; + break; + } + + if ( pUpperFrame->IsFlyFrame() ) + { + pUpperFrame = static_cast<const SwFlyFrame*>(pUpperFrame)->GetAnchorFrame(); + } + else + { + pUpperFrame = pUpperFrame->GetUpper(); + } + } + } + } + } + } + + if ( !aRequestedAttributes.hasElements() ) + { + rDefAttrSeq = aDefAttrSeq; + } + else + { + for( const OUString& rReqAttr : aRequestedAttributes ) + { + tAccParaPropValMap::const_iterator const aIter = aDefAttrSeq.find( rReqAttr ); + if ( aIter != aDefAttrSeq.end() ) + { + rDefAttrSeq[ aIter->first ] = aIter->second; + } + } + } +} + +uno::Sequence< PropertyValue > SwAccessibleParagraph::getDefaultAttributes( + const uno::Sequence< OUString >& aRequestedAttributes ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + tAccParaPropValMap aDefAttrSeq; + _getDefaultAttributesImpl( aRequestedAttributes, aDefAttrSeq ); + + // #i92233# + constexpr OUStringLiteral sMMToPixelRatio = u"MMToPixelRatio"; + bool bProvideMMToPixelRatio( !aRequestedAttributes.hasElements() || + (comphelper::findValue(aRequestedAttributes, sMMToPixelRatio) != -1) ); + + uno::Sequence< PropertyValue > aValues( aDefAttrSeq.size() + + ( bProvideMMToPixelRatio ? 1 : 0 ) ); + auto pValues = aValues.getArray(); + std::transform(aDefAttrSeq.begin(), aDefAttrSeq.end(), pValues, + [](const auto& rEntry) -> PropertyValue { return rEntry.second; }); + + // #i92233# + if ( bProvideMMToPixelRatio ) + { + PropertyValue rPropVal; + rPropVal.Name = sMMToPixelRatio; + const Size a100thMMSize( 1000, 1000 ); + const Size aPixelSize = GetMap()->LogicToPixel( a100thMMSize ); + const float fRatio = (static_cast<float>(a100thMMSize.Width())/100)/aPixelSize.Width(); + rPropVal.Value <<= fRatio; + rPropVal.Handle = -1; + rPropVal.State = beans::PropertyState_DEFAULT_VALUE; + pValues[ aValues.getLength() - 1 ] = rPropVal; + } + + return aValues; +} + +void SwAccessibleParagraph::_getRunAttributesImpl( + const sal_Int32 nIndex, + const uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rRunAttrSeq ) +{ + // create PaM for character at position <nIndex> + std::optional<SwPaM> pPaM; + const TextFrameIndex nCorePos(GetPortionData().GetCoreViewPosition(nIndex)); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + SwPosition const aModelPos(pFrame->MapViewToModelPos(nCorePos)); + SwTextNode *const pTextNode(aModelPos.nNode.GetNode().GetTextNode()); + { + SwPosition const aEndPos(*pTextNode, + aModelPos.nContent.GetIndex() == pTextNode->Len() + ? pTextNode->Len() // ??? + : aModelPos.nContent.GetIndex() + 1); + pPaM.emplace(aModelPos, aEndPos); + } + + // retrieve character attributes for the created PaM <pPaM> + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END -1> aSet( pPaM->GetDoc().GetAttrPool() ); + // #i82637# + // From the perspective of the a11y API the character attributes, which + // are set at the automatic paragraph style of the paragraph, are treated + // as run attributes. + // SwXTextCursor::GetCursorAttr( *pPaM, aSet, sal_True, sal_True ); + // get character attributes from automatic paragraph style and merge these into <aSet> + { + if ( pTextNode->HasSwAttrSet() ) + { + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END -1> aAutomaticParaStyleCharAttrs( pPaM->GetDoc().GetAttrPool()); + aAutomaticParaStyleCharAttrs.Put( *(pTextNode->GetpSwAttrSet()), false ); + aSet.Put( aAutomaticParaStyleCharAttrs ); + } + } + // get character attributes at <pPaM> and merge these into <aSet> + { + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END -1> aCharAttrsAtPaM( pPaM->GetDoc().GetAttrPool() ); + SwUnoCursorHelper::GetCursorAttr(*pPaM, aCharAttrsAtPaM, true); + aSet.Put( aCharAttrsAtPaM ); + } + + // build-up sequence containing the run attributes <rRunAttrSeq> + { + tAccParaPropValMap aRunAttrSeq; + { + tAccParaPropValMap aDefAttrSeq; + uno::Sequence< OUString > aDummy; + _getDefaultAttributesImpl( aDummy, aDefAttrSeq, true ); // #i82637# + + const SfxItemPropertyMap& rPropMap = + aSwMapProvider.GetPropertySet( PROPERTY_MAP_TEXT_CURSOR )->getPropertyMap(); + for ( const auto pEntry : rPropMap.getPropertyEntries() ) + { + const SfxPoolItem* pItem( nullptr ); + // #i82637# - Found character attributes, whose value equals the value of + // the corresponding default character attributes, are excluded. + if ( aSet.GetItemState( pEntry->nWID, true, &pItem ) == SfxItemState::SET ) + { + uno::Any aVal; + pItem->QueryValue( aVal, pEntry->nMemberId ); + + PropertyValue rPropVal; + rPropVal.Name = pEntry->aName; + rPropVal.Value = aVal; + rPropVal.Handle = -1; + rPropVal.State = PropertyState_DIRECT_VALUE; + + tAccParaPropValMap::const_iterator aDefIter = + aDefAttrSeq.find( rPropVal.Name ); + if ( aDefIter == aDefAttrSeq.end() || + rPropVal.Value != aDefIter->second.Value ) + { + aRunAttrSeq[rPropVal.Name] = rPropVal; + } + } + } + } + + if ( !aRequestedAttributes.hasElements() ) + { + rRunAttrSeq = aRunAttrSeq; + } + else + { + for( const OUString& rReqAttr : aRequestedAttributes ) + { + tAccParaPropValMap::iterator aIter = aRunAttrSeq.find( rReqAttr ); + if ( aIter != aRunAttrSeq.end() ) + { + rRunAttrSeq[ (*aIter).first ] = (*aIter).second; + } + } + } + } +} + +uno::Sequence< PropertyValue > SwAccessibleParagraph::getRunAttributes( + sal_Int32 nIndex, + const uno::Sequence< OUString >& aRequestedAttributes ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + { + const OUString& rText = GetString(); + if (!IsValidPosition(nIndex, rText.getLength())) + { + throw lang::IndexOutOfBoundsException(); + } + } + + tAccParaPropValMap aRunAttrSeq; + _getRunAttributesImpl( nIndex, aRequestedAttributes, aRunAttrSeq ); + + return comphelper::mapValuesToSequence( aRunAttrSeq ); +} + +void SwAccessibleParagraph::_getSupplementalAttributesImpl( + const uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rSupplementalAttrSeq ) +{ + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + const SwTextNode *const pTextNode(pFrame->GetTextNodeForParaProps()); + SfxItemSetFixed< + RES_PARATR_LINESPACING, RES_PARATR_ADJUST, + RES_PARATR_TABSTOP, RES_PARATR_TABSTOP, + RES_PARATR_NUMRULE, RES_PARATR_NUMRULE, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END - 1, + RES_LR_SPACE, RES_UL_SPACE> + aSet( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()) ); + + if ( pTextNode->HasBullet() || pTextNode->HasNumber() ) + { + aSet.Put( pTextNode->GetAttr(RES_PARATR_LIST_LEVEL) ); + } + aSet.Put( pTextNode->SwContentNode::GetAttr(RES_UL_SPACE) ); + aSet.Put( pTextNode->SwContentNode::GetAttr(RES_LR_SPACE) ); + aSet.Put( pTextNode->SwContentNode::GetAttr(RES_PARATR_ADJUST) ); + + tAccParaPropValMap aSupplementalAttrSeq; + { + const SfxItemPropertyMapEntry* pPropMap( + aSwMapProvider.GetPropertyMapEntries( PROPERTY_MAP_ACCESSIBILITY_TEXT_ATTRIBUTE ) ); + while ( !pPropMap->aName.isEmpty() ) + { + const SfxPoolItem* pItem = aSet.GetItem( pPropMap->nWID ); + if ( pItem ) + { + uno::Any aVal; + pItem->QueryValue( aVal, pPropMap->nMemberId ); + + PropertyValue rPropVal; + rPropVal.Name = pPropMap->aName; + rPropVal.Value = aVal; + rPropVal.Handle = -1; + rPropVal.State = beans::PropertyState_DEFAULT_VALUE; + + aSupplementalAttrSeq[rPropVal.Name] = rPropVal; + } + + ++pPropMap; + } + } + + for( const OUString& rSupplementalAttr : aRequestedAttributes ) + { + tAccParaPropValMap::const_iterator const aIter = aSupplementalAttrSeq.find( rSupplementalAttr ); + if ( aIter != aSupplementalAttrSeq.end() ) + { + rSupplementalAttrSeq[ aIter->first ] = aIter->second; + } + } +} + +void SwAccessibleParagraph::_correctValues( const sal_Int32 nIndex, + std::vector< PropertyValue >& rValues) +{ + PropertyValue ChangeAttr, ChangeAttrColor; + + const SwRangeRedline* pRedline = GetRedlineAtIndex(); + if ( pRedline ) + { + + const SwModuleOptions *pOpt = SW_MOD()->GetModuleConfig(); + AuthorCharAttr aChangeAttr; + if ( pOpt ) + { + switch( pRedline->GetType()) + { + case RedlineType::Insert: + aChangeAttr = pOpt->GetInsertAuthorAttr(); + break; + case RedlineType::Delete: + aChangeAttr = pOpt->GetDeletedAuthorAttr(); + break; + case RedlineType::Format: + aChangeAttr = pOpt->GetFormatAuthorAttr(); + break; + default: break; + } + } + switch( aChangeAttr.m_nItemId ) + { + case SID_ATTR_CHAR_WEIGHT: + ChangeAttr.Name = UNO_NAME_CHAR_WEIGHT; + ChangeAttr.Value <<= awt::FontWeight::BOLD; + break; + case SID_ATTR_CHAR_POSTURE: + ChangeAttr.Name = UNO_NAME_CHAR_POSTURE; + ChangeAttr.Value <<= awt::FontSlant_ITALIC; //char posture + break; + case SID_ATTR_CHAR_STRIKEOUT: + ChangeAttr.Name = UNO_NAME_CHAR_STRIKEOUT; + ChangeAttr.Value <<= awt::FontStrikeout::SINGLE; //char strikeout + break; + case SID_ATTR_CHAR_UNDERLINE: + ChangeAttr.Name = UNO_NAME_CHAR_UNDERLINE; + ChangeAttr.Value <<= aChangeAttr.m_nAttr; //underline line + break; + } + if( aChangeAttr.m_nColor != COL_NONE_COLOR ) + { + if( aChangeAttr.m_nItemId == SID_ATTR_BRUSH ) + { + ChangeAttrColor.Name = UNO_NAME_CHAR_BACK_COLOR; + if( aChangeAttr.m_nColor == COL_TRANSPARENT )//char backcolor + ChangeAttrColor.Value <<= COL_BLUE; + else + ChangeAttrColor.Value <<= aChangeAttr.m_nColor; + } + else + { + ChangeAttrColor.Name = UNO_NAME_CHAR_COLOR; + if( aChangeAttr.m_nColor == COL_TRANSPARENT )//char color + ChangeAttrColor.Value <<= COL_BLUE; + else + ChangeAttrColor.Value <<= aChangeAttr.m_nColor; + } + } + } + + // sw_redlinehide: this function only needs SwWrongList for 1 character, + // and the end is excluded by InWrongWord(), + // so it ought to work to just pick the wrong-list/node that contains + // the character following the given nIndex + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + TextFrameIndex const nCorePos(GetPortionData().GetCoreViewPosition(nIndex)); + std::pair<SwTextNode*, sal_Int32> pos(pFrame->MapViewToModel(nCorePos)); + if (pos.first->Len() == pos.second + && nCorePos != TextFrameIndex(pFrame->GetText().getLength())) + { + pos = pFrame->MapViewToModel(nCorePos + TextFrameIndex(1)); // try this one instead + assert(pos.first->Len() != pos.second); + } + const SwTextNode *const pTextNode(pos.first); + + sal_Int32 nValues = rValues.size(); + for (sal_Int32 i = 0; i < nValues; ++i) + { + PropertyValue& rValue = rValues[i]; + + if (rValue.Name == ChangeAttr.Name ) + { + rValue.Value = ChangeAttr.Value; + continue; + } + + if (rValue.Name == ChangeAttrColor.Name ) + { + rValue.Value = ChangeAttrColor.Value; + continue; + } + + //back color + if (rValue.Name == UNO_NAME_CHAR_BACK_COLOR) + { + uno::Any &anyChar = rValue.Value; + sal_uInt32 crBack = static_cast<sal_uInt32>( reinterpret_cast<sal_uIntPtr>(anyChar.pReserved)); + if (COL_AUTO == Color(ColorTransparency, crBack)) + { + uno::Reference<XAccessibleComponent> xComponent(this); + if (xComponent.is()) + { + crBack = static_cast<sal_uInt32>(xComponent->getBackground()); + } + rValue.Value <<= crBack; + } + continue; + } + + //char color + if (rValue.Name == UNO_NAME_CHAR_COLOR) + { + if( GetPortionData().IsInGrayPortion( nIndex ) ) + rValue.Value <<= SwViewOption::GetFieldShadingsColor(); + uno::Any &anyChar = rValue.Value; + sal_uInt32 crChar = static_cast<sal_uInt32>( reinterpret_cast<sal_uIntPtr>(anyChar.pReserved)); + + if( COL_AUTO == Color(ColorTransparency, crChar) ) + { + uno::Reference<XAccessibleComponent> xComponent(this); + if (xComponent.is()) + { + Color cr(ColorTransparency, xComponent->getBackground()); + crChar = sal_uInt32(cr.IsDark() ? COL_WHITE : COL_BLACK); + rValue.Value <<= crChar; + } + } + continue; + } + + // UnderLine + if (rValue.Name == UNO_NAME_CHAR_UNDERLINE) + { + //misspelled word + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr && pCursorShell->GetViewOptions() && pCursorShell->GetViewOptions()->IsOnlineSpell()) + { + const SwWrongList* pWrongList = pTextNode->GetWrong(); + if( nullptr != pWrongList ) + { + sal_Int32 nBegin = pos.second; + sal_Int32 nLen = 1; + if (pWrongList->InWrongWord(nBegin, nLen) && !pTextNode->IsSymbolAt(nBegin)) + { + rValue.Value <<= sal_uInt16(LINESTYLE_WAVE); + } + } + } + continue; + } + + // UnderLineColor + if (rValue.Name == UNO_NAME_CHAR_UNDERLINE_COLOR) + { + //misspelled word + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr && pCursorShell->GetViewOptions() && pCursorShell->GetViewOptions()->IsOnlineSpell()) + { + const SwWrongList* pWrongList = pTextNode->GetWrong(); + if( nullptr != pWrongList ) + { + sal_Int32 nBegin = pos.second; + sal_Int32 nLen = 1; + if (pWrongList->InWrongWord(nBegin, nLen) && !pTextNode->IsSymbolAt(nBegin)) + { + rValue.Value <<= sal_Int32(0x00ff0000); + continue; + } + } + } + + uno::Any &anyChar = rValue.Value; + sal_uInt32 crUnderline = static_cast<sal_uInt32>( reinterpret_cast<sal_uIntPtr>(anyChar.pReserved)); + if ( COL_AUTO == Color(ColorTransparency, crUnderline) ) + { + uno::Reference<XAccessibleComponent> xComponent(this); + if (xComponent.is()) + { + Color cr(ColorTransparency, xComponent->getBackground()); + crUnderline = sal_uInt32(cr.IsDark() ? COL_WHITE : COL_BLACK); + rValue.Value <<= crUnderline; + } + } + + continue; + } + + //tab stop + if (rValue.Name == UNO_NAME_TABSTOPS) + { + css::uno::Sequence< css::style::TabStop > tabs = GetCurrentTabStop( nIndex ); + if( !tabs.hasElements() ) + { + css::style::TabStop ts; + css::awt::Rectangle rc0 = getCharacterBounds(0); + css::awt::Rectangle rc1 = getCharacterBounds(nIndex); + if( rc1.X - rc0.X >= 48 ) + ts.Position = (rc1.X - rc0.X) - (rc1.X - rc0.X - 48)% 47 + 47; + else + ts.Position = 48; + ts.DecimalChar = ' '; + ts.FillChar = ' '; + ts.Alignment = css::style::TabAlign_LEFT; + tabs = { ts }; + } + rValue.Value <<= tabs; + continue; + } + + //footnote & endnote + if (rValue.Name == UNO_NAME_CHAR_ESCAPEMENT) + { + if ( GetPortionData().IsIndexInFootnode(nIndex) ) + { + rValue.Value <<= sal_Int32(101); + } + continue; + } + } +} + +awt::Rectangle SwAccessibleParagraph::getCharacterBounds( + sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // #i12332# The position after the string needs special treatment. + // IsValidChar -> IsValidPosition + if( ! (IsValidPosition( nIndex, GetString().getLength() ) ) ) + throw lang::IndexOutOfBoundsException(); + + // #i12332# + bool bBehindText = false; + if ( nIndex == GetString().getLength() ) + bBehindText = true; + + // get model position & prepare GetCharRect() arguments + SwCursorMoveState aMoveState; + aMoveState.m_bRealHeight = true; + aMoveState.m_bRealWidth = true; + SwSpecialPos aSpecialPos; + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + + /** #i12332# FillSpecialPos does not accept nIndex == + GetString().getLength(). In that case nPos is set to the + length of the string in the core. This way GetCharRect + returns the rectangle for a cursor at the end of the + paragraph. */ + const TextFrameIndex nPos = bBehindText + ? TextFrameIndex(pFrame->GetText().getLength()) + : GetPortionData().FillSpecialPos(nIndex, aSpecialPos, aMoveState.m_pSpecialPos ); + + // call GetCharRect + SwRect aCoreRect; + SwPosition aPosition(pFrame->MapViewToModelPos(nPos)); + GetFrame()->GetCharRect( aCoreRect, aPosition, &aMoveState ); + + // translate core coordinates into accessibility coordinates + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + tools::Rectangle aScreenRect( GetMap()->CoreToPixel( aCoreRect )); + SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root + + Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds ).TopLeft() ); + aScreenRect.Move( -aFramePixPos.getX(), -aFramePixPos.getY() ); + + // convert into AWT Rectangle + return awt::Rectangle( + aScreenRect.Left(), aScreenRect.Top(), + aScreenRect.GetWidth(), aScreenRect.GetHeight() ); +} + +sal_Int32 SwAccessibleParagraph::getCharacterCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return GetString().getLength(); +} + +sal_Int32 SwAccessibleParagraph::getIndexAtPoint( const awt::Point& rPoint ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // construct Point (translate into layout coordinates) + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + Point aPoint( rPoint.X, rPoint.Y ); + SwRect aLogBounds( GetBounds( *(GetMap()), GetFrame() ) ); // twip rel to doc root + Point aPixPos( GetMap()->CoreToPixel( aLogBounds ).TopLeft() ); + aPoint.setX(aPoint.getX() + aPixPos.getX()); + aPoint.setY(aPoint.getY() + aPixPos.getY()); + Point aCorePoint( GetMap()->PixelToCore( aPoint ) ); + if( !aLogBounds.Contains( aCorePoint ) ) + { + // #i12332# rPoint is may also be in rectangle returned by + // getCharacterBounds(getCharacterCount() + + awt::Rectangle aRectEndPos = + getCharacterBounds(getCharacterCount()); + + if (rPoint.X - aRectEndPos.X >= 0 && + rPoint.X - aRectEndPos.X < aRectEndPos.Width && + rPoint.Y - aRectEndPos.Y >= 0 && + rPoint.Y - aRectEndPos.Y < aRectEndPos.Height) + return getCharacterCount(); + + return -1; + } + + // ask core for position + OSL_ENSURE( GetFrame() != nullptr, "The text frame has vanished!" ); + OSL_ENSURE( GetFrame()->IsTextFrame(), "The text frame has mutated!" ); + const SwTextFrame* pFrame = static_cast<const SwTextFrame*>( GetFrame() ); + // construct SwPosition (where GetModelPositionForViewPoint() will put the result into) + SwTextNode* pNode = const_cast<SwTextNode*>(pFrame->GetTextNodeFirst()); + SwPosition aPos(*pNode, 0); + SwCursorMoveState aMoveState; + aMoveState.m_bPosMatchesBounds = true; + const bool bSuccess = pFrame->GetModelPositionForViewPoint( &aPos, aCorePoint, &aMoveState ); + + TextFrameIndex nIndex = pFrame->MapModelToViewPos(aPos); + if (TextFrameIndex(0) < nIndex) + { + assert(bSuccess); + SwRect aResultRect; + pFrame->GetCharRect( aResultRect, aPos ); + bool bVert = pFrame->IsVertical(); + bool bR2L = pFrame->IsRightToLeft(); + + if ( (!bVert && aResultRect.Pos().getX() > aCorePoint.getX()) || + ( bVert && aResultRect.Pos().getY() > aCorePoint.getY()) || + ( bR2L && aResultRect.Right() < aCorePoint.getX()) ) + { + SwPosition aPosPrev(pFrame->MapViewToModelPos(nIndex - TextFrameIndex(1))); + SwRect aResultRectPrev; + pFrame->GetCharRect( aResultRectPrev, aPosPrev ); + if ( (!bVert && aResultRectPrev.Pos().getX() < aCorePoint.getX() && aResultRect.Pos().getY() == aResultRectPrev.Pos().getY()) || + ( bVert && aResultRectPrev.Pos().getY() < aCorePoint.getY() && aResultRect.Pos().getX() == aResultRectPrev.Pos().getX()) || + ( bR2L && aResultRectPrev.Right() > aCorePoint.getX() && aResultRect.Pos().getY() == aResultRectPrev.Pos().getY()) ) + { + --nIndex; + } + } + } + + return bSuccess + ? GetPortionData().GetAccessiblePosition(nIndex) + : -1; +} + +OUString SwAccessibleParagraph::getSelectedText() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart, nEnd; + bool bSelected = GetSelection( nStart, nEnd ); + return bSelected + ? GetString().copy( nStart, nEnd - nStart ) + : OUString(); +} + +sal_Int32 SwAccessibleParagraph::getSelectionStart() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart, nEnd; + GetSelection( nStart, nEnd ); + return nStart; +} + +sal_Int32 SwAccessibleParagraph::getSelectionEnd() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart, nEnd; + GetSelection( nStart, nEnd ); + return nEnd; +} + +sal_Bool SwAccessibleParagraph::setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking + sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidRange( nStartIndex, nEndIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + bool bRet = false; + + // get cursor shell + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + // create pam for selection + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + TextFrameIndex const nStart(GetPortionData().GetCoreViewPosition(nStartIndex)); + TextFrameIndex const nEnd(GetPortionData().GetCoreViewPosition(nEndIndex)); + SwPaM aPaM(pFrame->MapViewToModelPos(nStart)); + aPaM.SetMark(); + *aPaM.GetPoint() = pFrame->MapViewToModelPos(nEnd); + + // set PaM at cursor shell + bRet = Select( aPaM ); + } + + return bRet; +} + +OUString SwAccessibleParagraph::getText() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return GetString(); +} + +OUString SwAccessibleParagraph::getTextRange( + sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + OUString sText( GetString() ); + + if ( !IsValidRange( nStartIndex, nEndIndex, sText.getLength() ) ) + throw lang::IndexOutOfBoundsException(); + + OrderRange( nStartIndex, nEndIndex ); + return sText.copy(nStartIndex, nEndIndex-nStartIndex ); +} + +/*accessibility::*/TextSegment SwAccessibleParagraph::getTextAtIndex( sal_Int32 nIndex, sal_Int16 nTextType ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + /*accessibility::*/TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + const OUString rText = GetString(); + // implement the silly specification that first position after + // text must return an empty string, rather than throwing an + // IndexOutOfBoundsException, except for LINE, where the last + // line is returned + if( nIndex == rText.getLength() && AccessibleTextType::LINE != nTextType ) + return aResult; + + // with error checking + i18n::Boundary aBound; + bool bWord = GetTextBoundary( aBound, rText, nIndex, nTextType ); + + OSL_ENSURE( aBound.startPos >= 0, "illegal boundary" ); + OSL_ENSURE( aBound.startPos <= aBound.endPos, "illegal boundary" ); + + // return word (if present) + if ( bWord ) + { + aResult.SegmentText = rText.copy( aBound.startPos, aBound.endPos - aBound.startPos ); + aResult.SegmentStart = aBound.startPos; + aResult.SegmentEnd = aBound.endPos; + } + + return aResult; +} + +/*accessibility::*/TextSegment SwAccessibleParagraph::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 nTextType ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const OUString rText = GetString(); + + /*accessibility::*/TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + //If nIndex = 0, then nobefore text so return -1 directly. + if( nIndex == 0 ) + return aResult; + //Tab will be return when call WORDTYPE + + // get starting pos + i18n::Boundary aBound; + if (nIndex == rText.getLength()) + aBound.startPos = aBound.endPos = nIndex; + else + { + bool bTmp = GetTextBoundary( aBound, rText, nIndex, nTextType ); + + if ( ! bTmp ) + aBound.startPos = aBound.endPos = nIndex; + } + + // now skip to previous word + if (nTextType==2 || nTextType == 3) + { + i18n::Boundary preBound = aBound; + while(preBound.startPos==aBound.startPos && nIndex > 0) + { + nIndex = min( nIndex, preBound.startPos ) - 1; + if( nIndex < 0 ) break; + GetTextBoundary( preBound, rText, nIndex, nTextType ); + } + //if (nIndex>0) + if (nIndex>=0) + //Tab will be return when call WORDTYPE + { + aResult.SegmentText = rText.copy( preBound.startPos, preBound.endPos - preBound.startPos ); + aResult.SegmentStart = preBound.startPos; + aResult.SegmentEnd = preBound.endPos; + } + } + else + { + bool bWord = false; + while( !bWord ) + { + nIndex = min( nIndex, aBound.startPos ) - 1; + if( nIndex >= 0 ) + { + bWord = GetTextBoundary( aBound, rText, nIndex, nTextType ); + } + else + break; // exit if beginning of string is reached + } + + if (bWord && nIndex<rText.getLength()) + { + aResult.SegmentText = rText.copy( aBound.startPos, aBound.endPos - aBound.startPos ); + aResult.SegmentStart = aBound.startPos; + aResult.SegmentEnd = aBound.endPos; + } + } + return aResult; +} + +/*accessibility::*/TextSegment SwAccessibleParagraph::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 nTextType ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + /*accessibility::*/TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + const OUString rText = GetString(); + + // implement the silly specification that first position after + // text must return an empty string, rather than throwing an + // IndexOutOfBoundsException + if( nIndex == rText.getLength() ) + return aResult; + + // get first word, then skip to next word + i18n::Boundary aBound; + GetTextBoundary( aBound, rText, nIndex, nTextType ); + bool bWord = false; + while( !bWord ) + { + nIndex = max( sal_Int32(nIndex+1), aBound.endPos ); + if( nIndex < rText.getLength() ) + bWord = GetTextBoundary( aBound, rText, nIndex, nTextType ); + else + break; // exit if end of string is reached + } + + if ( bWord ) + { + aResult.SegmentText = rText.copy( aBound.startPos, aBound.endPos - aBound.startPos ); + aResult.SegmentStart = aBound.startPos; + aResult.SegmentEnd = aBound.endPos; + } + +/* + sal_Bool bWord = sal_False; + bWord = GetTextBoundary( aBound, rText, nIndex, nTextType ); + + if (nTextType==2) + { + Boundary nexBound=aBound; + + // real current word + if( nIndex <= aBound.endPos && nIndex >= aBound.startPos ) + { + while(nexBound.endPos==aBound.endPos&&nIndex<rText.getLength()) + { + // nIndex = max( (sal_Int32)(nIndex), nexBound.endPos) + 1; + nIndex = max( (sal_Int32)(nIndex), nexBound.endPos) ; + const sal_Unicode* pStr = rText.getStr(); + if (pStr) + { + if( pStr[nIndex] == sal_Unicode(' ') ) + nIndex++; + } + if( nIndex < rText.getLength() ) + { + bWord = GetTextBoundary( nexBound, rText, nIndex, nTextType ); + } + } + } + + if (bWord && nIndex<rText.getLength()) + { + aResult.SegmentText = rText.copy( nexBound.startPos, nexBound.endPos - nexBound.startPos ); + aResult.SegmentStart = nexBound.startPos; + aResult.SegmentEnd = nexBound.endPos; + } + + } + else + { + bWord = sal_False; + while( !bWord ) + { + nIndex = max( (sal_Int32)(nIndex+1), aBound.endPos ); + if( nIndex < rText.getLength() ) + { + bWord = GetTextBoundary( aBound, rText, nIndex, nTextType ); + } + else + break; // exit if end of string is reached + } + if (bWord && nIndex<rText.getLength()) + { + aResult.SegmentText = rText.copy( aBound.startPos, aBound.endPos - aBound.startPos ); + aResult.SegmentStart = aBound.startPos; + aResult.SegmentEnd = aBound.endPos; + } + } +*/ + return aResult; +} + +sal_Bool SwAccessibleParagraph::copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // select and copy (through dispatch mechanism) + setSelection( nStartIndex, nEndIndex ); + ExecuteAtViewShell( SID_COPY ); + return true; +} + +sal_Bool SwAccessibleParagraph::scrollSubstringTo( sal_Int32 nStartIndex, + sal_Int32 nEndIndex, AccessibleScrollType aScrollType ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking + sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidRange( nStartIndex, nEndIndex, nLength ) ) + throw lang::IndexOutOfBoundsException(); + + vcl::Window *pWin = GetWindow(); + if ( ! pWin ) + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + + /* Start and end character bounds, in pixels, relative to the paragraph */ + awt::Rectangle startR, endR; + startR = getCharacterBounds(nStartIndex); + endR = getCharacterBounds(nEndIndex); + + /* Adjust points to fit the bounding box of both bounds. */ + Point sP(std::min(startR.X, endR.X), startR.Y); + Point eP(std::max(startR.X + startR.Width, endR.X + endR.Width), endR.Y + endR.Height); + + /* Offset the values relative to the view shell frame */ + SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root + Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds ).TopLeft() ); + sP += aFramePixPos; + eP += aFramePixPos; + + Point startPoint(GetMap()->PixelToCore(sP)); + Point endPoint(GetMap()->PixelToCore(eP)); + + switch (aScrollType) + { +#ifdef notyet + case AccessibleScrollType_SCROLL_TOP_LEFT: + break; + case AccessibleScrollType_SCROLL_BOTTOM_RIGHT: + break; + case AccessibleScrollType_SCROLL_TOP_EDGE: + break; + case AccessibleScrollType_SCROLL_BOTTOM_EDGE: + break; + case AccessibleScrollType_SCROLL_LEFT_EDGE: + break; + case AccessibleScrollType_SCROLL_RIGHT_EDGE: + break; +#endif + case AccessibleScrollType_SCROLL_ANYWHERE: + break; + default: + return false; + } + + const SwRect aRect(startPoint, endPoint); + SwViewShell* pViewShell = GetMap()->GetShell(); + OSL_ENSURE( pViewShell != nullptr, "View shell expected!" ); + + ScrollMDI(pViewShell, aRect, USHRT_MAX, USHRT_MAX); + + return true; +} + +// XAccessibleEditableText + +sal_Bool SwAccessibleParagraph::cutText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( !IsEditableState() ) + return false; + + // select and cut (through dispatch mechanism) + setSelection( nStartIndex, nEndIndex ); + ExecuteAtViewShell( SID_CUT ); + return true; +} + +sal_Bool SwAccessibleParagraph::pasteText( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( !IsEditableState() ) + return false; + + // select and paste (through dispatch mechanism) + setSelection( nIndex, nIndex ); + ExecuteAtViewShell( SID_PASTE ); + return true; +} + +sal_Bool SwAccessibleParagraph::deleteText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + return replaceText( nStartIndex, nEndIndex, OUString() ); +} + +sal_Bool SwAccessibleParagraph::insertText( const OUString& sText, sal_Int32 nIndex ) +{ + return replaceText( nIndex, nIndex, sText ); +} + +sal_Bool SwAccessibleParagraph::replaceText( + sal_Int32 nStartIndex, sal_Int32 nEndIndex, + const OUString& sReplacement ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const OUString& rText = GetString(); + + if( !IsValidRange( nStartIndex, nEndIndex, rText.getLength() ) ) + throw lang::IndexOutOfBoundsException(); + + if( !IsEditableState() ) + return false; + + // translate positions + TextFrameIndex nStart; + TextFrameIndex nEnd; + bool bSuccess = GetPortionData().GetEditableRange( + nStartIndex, nEndIndex, nStart, nEnd ); + + // edit only if the range is editable + if( bSuccess ) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + // create SwPosition for nStartIndex + SwPosition aStartPos(pFrame->MapViewToModelPos(nStart)); + + // create SwPosition for nEndIndex + SwPosition aEndPos(pFrame->MapViewToModelPos(nEnd)); + + // now create XTextRange as helper and set string + const uno::Reference<text::XTextRange> xRange( + SwXTextRange::CreateXTextRange( + const_cast<SwDoc&>(pFrame->GetDoc()), aStartPos, &aEndPos)); + xRange->setString(sReplacement); + + // delete portion data + ClearPortionData(); + } + + return bSuccess; + +} + +sal_Bool SwAccessibleParagraph::setAttributes( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex, + const uno::Sequence<PropertyValue>& rAttributeSet ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const OUString& rText = GetString(); + + if( ! IsValidRange( nStartIndex, nEndIndex, rText.getLength() ) ) + throw lang::IndexOutOfBoundsException(); + + if( !IsEditableState() ) + return false; + + // create a (dummy) text portion for the sole purpose of calling + // setPropertyValue on it + rtl::Reference<SwXTextPortion> xPortion = CreateUnoPortion( nStartIndex, + nEndIndex ); + + // build sorted index array + sal_Int32 nLength = rAttributeSet.getLength(); + const PropertyValue* pPairs = rAttributeSet.getConstArray(); + std::vector<sal_Int32> aIndices(nLength); + std::iota(aIndices.begin(), aIndices.end(), 0); + std::sort(aIndices.begin(), aIndices.end(), IndexCompare(pPairs)); + + // create sorted sequences according to index array + uno::Sequence< OUString > aNames( nLength ); + OUString* pNames = aNames.getArray(); + uno::Sequence< uno::Any > aValues( nLength ); + uno::Any* pValues = aValues.getArray(); + for (sal_Int32 i = 0; i < nLength; ++i) + { + const PropertyValue& rVal = pPairs[aIndices[i]]; + pNames[i] = rVal.Name; + pValues[i] = rVal.Value; + } + aIndices.clear(); + + // now set the values + bool bRet = true; + try + { + xPortion->setPropertyValues( aNames, aValues ); + } + catch (const UnknownPropertyException&) + { + // error handling through return code! + bRet = false; + } + + return bRet; +} + +sal_Bool SwAccessibleParagraph::setText( const OUString& sText ) +{ + return replaceText(0, GetString().getLength(), sText); +} + +// XAccessibleSelection + +void SwAccessibleParagraph::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + + m_aSelectionHelper.selectAccessibleChild(nChildIndex); +} + +sal_Bool SwAccessibleParagraph::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + + return m_aSelectionHelper.isAccessibleChildSelected(nChildIndex); +} + +void SwAccessibleParagraph::clearAccessibleSelection( ) +{ + ThrowIfDisposed(); +} + +void SwAccessibleParagraph::selectAllAccessibleChildren( ) +{ + ThrowIfDisposed(); + + m_aSelectionHelper.selectAllAccessibleChildren(); +} + +sal_Int32 SwAccessibleParagraph::getSelectedAccessibleChildCount( ) +{ + ThrowIfDisposed(); + + return m_aSelectionHelper.getSelectedAccessibleChildCount(); +} + +uno::Reference<XAccessible> SwAccessibleParagraph::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + ThrowIfDisposed(); + + return m_aSelectionHelper.getSelectedAccessibleChild(nSelectedChildIndex); +} + +// index has to be treated as global child index. +void SwAccessibleParagraph::deselectAccessibleChild( + sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + + m_aSelectionHelper.deselectAccessibleChild( nChildIndex ); +} + +// XAccessibleHypertext + +namespace { + +class SwHyperlinkIter_Impl +{ + SwTextFrame const& m_rFrame; + sw::MergedAttrIter m_Iter; + TextFrameIndex m_nStt; + TextFrameIndex m_nEnd; + +public: + explicit SwHyperlinkIter_Impl(const SwTextFrame & rTextFrame); + const SwTextAttr *next(SwTextNode const** ppNode = nullptr); + + TextFrameIndex startIdx() const { return m_nStt; } + TextFrameIndex endIdx() const { return m_nEnd; } +}; + +} + +SwHyperlinkIter_Impl::SwHyperlinkIter_Impl(const SwTextFrame & rTextFrame) + : m_rFrame(rTextFrame) + , m_Iter(rTextFrame) + , m_nStt(rTextFrame.GetOffset()) +{ + const SwTextFrame *const pFollFrame = rTextFrame.GetFollow(); + m_nEnd = pFollFrame ? pFollFrame->GetOffset() : TextFrameIndex(rTextFrame.GetText().getLength()); +} + +const SwTextAttr *SwHyperlinkIter_Impl::next(SwTextNode const** ppNode) +{ + const SwTextAttr *pAttr = nullptr; + if (ppNode) + { + *ppNode = nullptr; + } + + SwTextNode const* pNode(nullptr); + while (SwTextAttr const*const pHt = m_Iter.NextAttr(&pNode)) + { + if (RES_TXTATR_INETFMT == pHt->Which()) + { + const TextFrameIndex nHtStt(m_rFrame.MapModelToView(pNode, pHt->GetStart())); + const TextFrameIndex nHtEnd(m_rFrame.MapModelToView(pNode, pHt->GetAnyEnd())); + if (nHtEnd > nHtStt && + ((nHtStt >= m_nStt && nHtStt < m_nEnd) || + (nHtEnd > m_nStt && nHtEnd <= m_nEnd))) + { + pAttr = pHt; + if (ppNode) + { + *ppNode = pNode; + } + break; + } + } + } + + return pAttr; +}; + +sal_Int32 SAL_CALL SwAccessibleParagraph::getHyperLinkCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nCount = 0; + // #i77108# - provide hyperlinks also in editable documents. + + const SwTextFrame *pTextFrame = static_cast<const SwTextFrame*>( GetFrame() ); + SwHyperlinkIter_Impl aIter(*pTextFrame); + while( aIter.next() ) + nCount++; + + return nCount; +} + +uno::Reference< XAccessibleHyperlink > SAL_CALL + SwAccessibleParagraph::getHyperLink( sal_Int32 nLinkIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + uno::Reference< XAccessibleHyperlink > xRet; + + const SwTextFrame *pTextFrame = static_cast<const SwTextFrame*>( GetFrame() ); + SwHyperlinkIter_Impl aHIter(*pTextFrame); + SwTextNode const* pNode(nullptr); + SwTextAttr* pHt = const_cast<SwTextAttr*>(aHIter.next(&pNode)); + for (sal_Int32 nTIndex = 0; pHt && nTIndex <= nLinkIndex; ++nTIndex) + { + if( nTIndex == nLinkIndex ) + { // found + if (!m_pHyperTextData) + m_pHyperTextData.reset( new SwAccessibleHyperTextData ); + SwAccessibleHyperTextData::iterator aIter = + m_pHyperTextData ->find( pHt ); + if (aIter != m_pHyperTextData->end()) + { + xRet = (*aIter).second; + } + if (!xRet.is()) + { + TextFrameIndex const nHintStart(pTextFrame->MapModelToView(pNode, pHt->GetStart())); + TextFrameIndex const nHintEnd(pTextFrame->MapModelToView(pNode, pHt->GetAnyEnd())); + const sal_Int32 nTmpHStt = GetPortionData().GetAccessiblePosition( + max(aHIter.startIdx(), nHintStart)); + const sal_Int32 nTmpHEnd = GetPortionData().GetAccessiblePosition( + min(aHIter.endIdx(), nHintEnd)); + xRet = new SwAccessibleHyperlink(*pHt, + *this, nTmpHStt, nTmpHEnd ); + if (aIter != m_pHyperTextData->end()) + { + (*aIter).second = xRet; + } + else + { + m_pHyperTextData->emplace( pHt, xRet ); + } + } + break; + } + + // iterate next hyperlink + pHt = const_cast<SwTextAttr*>(aHIter.next(&pNode)); + } + if( !xRet.is() ) + throw lang::IndexOutOfBoundsException(); + + return xRet; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getHyperLinkIndex( sal_Int32 nCharIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking + sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidPosition( nCharIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + sal_Int32 nRet = -1; + // #i77108# + { + const SwTextFrame *pTextFrame = static_cast<const SwTextFrame*>( GetFrame() ); + SwHyperlinkIter_Impl aHIter(*pTextFrame); + + const TextFrameIndex nIdx = GetPortionData().GetCoreViewPosition(nCharIndex); + sal_Int32 nPos = 0; + SwTextNode const* pNode(nullptr); + const SwTextAttr *pHt = aHIter.next(&pNode); + while (pHt && (nIdx < pTextFrame->MapModelToView(pNode, pHt->GetStart()) + || nIdx >= pTextFrame->MapModelToView(pNode, pHt->GetAnyEnd()))) + { + pHt = aHIter.next(&pNode); + nPos++; + } + + if( pHt ) + nRet = nPos; + } + + if (nRet == -1) + throw lang::IndexOutOfBoundsException(); + return nRet; +} + +// #i71360#, #i108125# - adjustments for change tracking text markup +sal_Int32 SAL_CALL SwAccessibleParagraph::getTextMarkupCount( sal_Int32 nTextMarkupType ) +{ + SolarMutexGuard g; + + std::unique_ptr<SwTextMarkupHelper> pTextMarkupHelper; + switch ( nTextMarkupType ) + { + case text::TextMarkupType::TRACK_CHANGE_INSERTION: + case text::TextMarkupType::TRACK_CHANGE_DELETION: + case text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE: + { + pTextMarkupHelper.reset( new SwTextMarkupHelper( + GetPortionData(), + *(mpParaChangeTrackInfo->getChangeTrackingTextMarkupList( nTextMarkupType ) )) ); + } + break; + default: + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + pTextMarkupHelper.reset(new SwTextMarkupHelper(GetPortionData(), *pFrame)); + } + } + + return pTextMarkupHelper->getTextMarkupCount( nTextMarkupType ); +} + +//MSAA Extension Implementation in app module +sal_Bool SAL_CALL SwAccessibleParagraph::scrollToPosition( const css::awt::Point&, sal_Bool ) +{ + return false; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getSelectedPortionCount( ) +{ + SolarMutexGuard g; + + sal_Int32 nSelected = 0; + SwPaM* pCursor = GetCursor( true ); + if( pCursor != nullptr ) + { + // get SwPosition for my node + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + SwNodeOffset nFirstNode(pFrame->GetTextNodeFirst()->GetIndex()); + SwNodeOffset nLastNode; + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + nLastNode = pMerged->pLastNode->GetIndex(); + } + else + { + nLastNode = nFirstNode; + } + + // iterate over ring + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + // ignore, if no mark + if( rTmpCursor.HasMark() ) + { + // check whether frame's node(s) are 'inside' pCursor + SwPosition* pStart = rTmpCursor.Start(); + SwNodeOffset nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = rTmpCursor.End(); + SwNodeOffset nEndIndex = pEnd->nNode.GetIndex(); + if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex)) + { + nSelected++; + } + // else: this PaM doesn't point to this paragraph + } + // else: this PaM is collapsed and doesn't select anything + } + } + return nSelected; + +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getSeletedPositionStart( sal_Int32 nSelectedPortionIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart=-1, nEnd=-1; + /*sal_Bool bSelected = */GetSelectionAtIndex(&nSelectedPortionIndex, nStart, nEnd ); + return nStart; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getSeletedPositionEnd( sal_Int32 nSelectedPortionIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart=-1, nEnd=-1; + /*sal_Bool bSelected = */GetSelectionAtIndex(&nSelectedPortionIndex, nStart, nEnd ); + return nEnd; +} + +sal_Bool SAL_CALL SwAccessibleParagraph::removeSelection( sal_Int32 selectionIndex ) +{ + SolarMutexGuard g; + + if(selectionIndex < 0) return false; + + sal_Int32 nSelected = selectionIndex; + + // get the selection, and test whether it affects our text node + SwPaM* pCursor = GetCursor( true ); + + if( pCursor != nullptr ) + { + bool bRet = false; + + // get SwPosition for my node + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + SwNodeOffset nFirstNode(pFrame->GetTextNodeFirst()->GetIndex()); + SwNodeOffset nLastNode; + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + nLastNode = pMerged->pLastNode->GetIndex(); + } + else + { + nLastNode = nFirstNode; + } + + // iterate over ring + SwPaM* pRingStart = pCursor; + do + { + // ignore, if no mark + if( pCursor->HasMark() ) + { + // check whether frame's node(s) are 'inside' pCursor + SwPosition* pStart = pCursor->Start(); + SwNodeOffset nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = pCursor->End(); + SwNodeOffset nEndIndex = pEnd->nNode.GetIndex(); + if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex)) + { + if( nSelected == 0 ) + { + pCursor->MoveTo(nullptr); + delete pCursor; + bRet = true; + } + else + { + nSelected--; + } + } + } + // else: this PaM is collapsed and doesn't select anything + if(!bRet) + pCursor = pCursor->GetNext(); + } + while( !bRet && (pCursor != pRingStart) ); + } + return true; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::addSelection( sal_Int32, sal_Int32 startOffset, sal_Int32 endOffset) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking + sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidRange( startOffset, endOffset, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + sal_Int32 nSelectedCount = getSelectedPortionCount(); + for ( sal_Int32 i = nSelectedCount ; i >= 0 ; i--) + { + sal_Int32 nStart, nEnd; + bool bSelected = GetSelectionAtIndex(&i, nStart, nEnd ); + if(bSelected) + { + if(nStart <= nEnd ) + { + if (( startOffset>=nStart && startOffset <=nEnd ) || //startOffset in a selection + ( endOffset>=nStart && endOffset <=nEnd ) || //endOffset in a selection + ( startOffset <= nStart && endOffset >=nEnd) || //start and end include the old selection + ( startOffset >= nStart && endOffset <=nEnd) ) + { + removeSelection(i); + } + + } + else + { + if (( startOffset>=nEnd && startOffset <=nStart ) || //startOffset in a selection + ( endOffset>=nEnd && endOffset <=nStart ) || //endOffset in a selection + ( startOffset <= nStart && endOffset >=nEnd) || //start and end include the old selection + ( startOffset >= nStart && endOffset <=nEnd) ) + + { + removeSelection(i); + } + } + } + + } + + // get cursor shell + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + // create pam for selection + pCursorShell->StartAction(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + SwPaM* aPaM = pCursorShell->CreateCursor(); + aPaM->SetMark(); + *aPaM->GetPoint() = pFrame->MapViewToModelPos(GetPortionData().GetCoreViewPosition(startOffset)); + *aPaM->GetMark() = pFrame->MapViewToModelPos(GetPortionData().GetCoreViewPosition(endOffset)); + pCursorShell->EndAction(); + } + + return 0; +} + +/*accessibility::*/TextSegment SAL_CALL + SwAccessibleParagraph::getTextMarkup( sal_Int32 nTextMarkupIndex, + sal_Int32 nTextMarkupType ) +{ + SolarMutexGuard g; + + std::unique_ptr<SwTextMarkupHelper> pTextMarkupHelper; + switch ( nTextMarkupType ) + { + case text::TextMarkupType::TRACK_CHANGE_INSERTION: + case text::TextMarkupType::TRACK_CHANGE_DELETION: + case text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE: + { + pTextMarkupHelper.reset( new SwTextMarkupHelper( + GetPortionData(), + *(mpParaChangeTrackInfo->getChangeTrackingTextMarkupList( nTextMarkupType ) )) ); + } + break; + default: + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + pTextMarkupHelper.reset(new SwTextMarkupHelper(GetPortionData(), *pFrame)); + } + } + + return pTextMarkupHelper->getTextMarkup( nTextMarkupIndex, nTextMarkupType ); +} + +uno::Sequence< /*accessibility::*/TextSegment > SAL_CALL + SwAccessibleParagraph::getTextMarkupAtIndex( sal_Int32 nCharIndex, + sal_Int32 nTextMarkupType ) +{ + SolarMutexGuard g; + + // parameter checking + const sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidPosition( nCharIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + std::unique_ptr<SwTextMarkupHelper> pTextMarkupHelper; + switch ( nTextMarkupType ) + { + case text::TextMarkupType::TRACK_CHANGE_INSERTION: + case text::TextMarkupType::TRACK_CHANGE_DELETION: + case text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE: + { + pTextMarkupHelper.reset( new SwTextMarkupHelper( + GetPortionData(), + *(mpParaChangeTrackInfo->getChangeTrackingTextMarkupList( nTextMarkupType ) )) ); + } + break; + default: + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + pTextMarkupHelper.reset(new SwTextMarkupHelper(GetPortionData(), *pFrame)); + } + } + + return pTextMarkupHelper->getTextMarkupAtIndex( nCharIndex, nTextMarkupType ); +} + +// #i89175# +sal_Int32 SAL_CALL SwAccessibleParagraph::getLineNumberAtIndex( sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + // parameter checking + const sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidPosition( nIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + const sal_Int32 nLineNo = GetPortionData().GetLineNo( nIndex ); + return nLineNo; +} + +/*accessibility::*/TextSegment SAL_CALL + SwAccessibleParagraph::getTextAtLineNumber( sal_Int32 nLineNo ) +{ + SolarMutexGuard g; + + // parameter checking + if ( nLineNo < 0 || + nLineNo >= GetPortionData().GetLineCount() ) + { + throw lang::IndexOutOfBoundsException(); + } + + i18n::Boundary aLineBound; + GetPortionData().GetBoundaryOfLine( nLineNo, aLineBound ); + + /*accessibility::*/TextSegment aTextAtLine; + const OUString rText = GetString(); + aTextAtLine.SegmentText = rText.copy( aLineBound.startPos, + aLineBound.endPos - aLineBound.startPos ); + aTextAtLine.SegmentStart = aLineBound.startPos; + aTextAtLine.SegmentEnd = aLineBound.endPos; + + return aTextAtLine; +} + +/*accessibility::*/TextSegment SAL_CALL SwAccessibleParagraph::getTextAtLineWithCaret() +{ + SolarMutexGuard g; + + const sal_Int32 nLineNoOfCaret = getNumberOfLineWithCaret(); + + if ( nLineNoOfCaret >= 0 && + nLineNoOfCaret < GetPortionData().GetLineCount() ) + { + return getTextAtLineNumber( nLineNoOfCaret ); + } + + return /*accessibility::*/TextSegment(); +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getNumberOfLineWithCaret() +{ + SolarMutexGuard g; + + const sal_Int32 nCaretPos = getCaretPosition(); + const sal_Int32 nLength = GetString().getLength(); + if ( !IsValidPosition( nCaretPos, nLength ) ) + { + return -1; + } + + sal_Int32 nLineNo = GetPortionData().GetLineNo( nCaretPos ); + + // special handling for cursor positioned at end of text line via End key + if ( nCaretPos != 0 ) + { + i18n::Boundary aLineBound; + GetPortionData().GetBoundaryOfLine( nLineNo, aLineBound ); + if ( nCaretPos == aLineBound.startPos ) + { + SwCursorShell* pCursorShell = SwAccessibleParagraph::GetCursorShell(); + if ( pCursorShell != nullptr ) + { + const awt::Rectangle aCharRect = getCharacterBounds( nCaretPos ); + + const SwRect& aCursorCoreRect = pCursorShell->GetCharRect(); + // translate core coordinates into accessibility coordinates + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast<cppu::OWeakObject*>(this)); + } + + tools::Rectangle aScreenRect( GetMap()->CoreToPixel( aCursorCoreRect )); + + SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root + Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds ).TopLeft() ); + aScreenRect.Move( -aFramePixPos.getX(), -aFramePixPos.getY() ); + + // convert into AWT Rectangle + const awt::Rectangle aCursorRect( aScreenRect.Left(), + aScreenRect.Top(), + aScreenRect.GetWidth(), + aScreenRect.GetHeight() ); + + if ( aCharRect.X != aCursorRect.X || + aCharRect.Y != aCursorRect.Y ) + { + --nLineNo; + } + } + } + } + + return nLineNo; +} + +// #i108125# +void SwAccessibleParagraph::Notify(SfxBroadcaster&, const SfxHint&) +{ + mpParaChangeTrackInfo->reset(); +} + +bool SwAccessibleParagraph::GetSelectionAtIndex( + sal_Int32 * pSelection, sal_Int32& nStart, sal_Int32& nEnd) +{ + if (pSelection && *pSelection < 0) return false; + + bool bRet = false; + nStart = -1; + nEnd = -1; + + // get the selection, and test whether it affects our text node + SwPaM* pCursor = GetCursor( true ); + if( pCursor != nullptr ) + { + // get SwPosition for my node + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(GetFrame())); + SwNodeOffset nFirstNode(pFrame->GetTextNodeFirst()->GetIndex()); + SwNodeOffset nLastNode; + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + nLastNode = pMerged->pLastNode->GetIndex(); + } + else + { + nLastNode = nFirstNode; + } + + // iterate over ring + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + // ignore, if no mark + if( rTmpCursor.HasMark() ) + { + // check whether frame's node(s) are 'inside' pCursor + SwPosition* pStart = rTmpCursor.Start(); + SwNodeOffset nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = rTmpCursor.End(); + SwNodeOffset nEndIndex = pEnd->nNode.GetIndex(); + if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex)) + { + if (!pSelection || *pSelection == 0) + { + // translate start and end positions + + // start position + sal_Int32 nLocalStart = -1; + if (nStartIndex < nFirstNode) + { + // selection starts in previous node: + // then our local selection starts with the paragraph + nLocalStart = 0; + } + else + { + assert(FrameContainsNode(*pFrame, nStartIndex)); + + // selection starts in this node: + // then check whether it's before or inside our part of + // the paragraph, and if so, get the proper position + const TextFrameIndex nCoreStart = + pFrame->MapModelToViewPos(*pStart); + if( nCoreStart < + GetPortionData().GetFirstValidCorePosition() ) + { + nLocalStart = 0; + } + else if( nCoreStart <= + GetPortionData().GetLastValidCorePosition() ) + { + SAL_WARN_IF( + !GetPortionData().IsValidCorePosition( + nCoreStart), + "sw.a11y", + "problem determining valid core position"); + + nLocalStart = + GetPortionData().GetAccessiblePosition( + nCoreStart ); + } + } + + // end position + sal_Int32 nLocalEnd = -1; + if (nLastNode < nEndIndex) + { + // selection ends in following node: + // then our local selection extends to the end + nLocalEnd = GetPortionData().GetAccessibleString(). + getLength(); + } + else + { + assert(FrameContainsNode(*pFrame, nEndIndex)); + + // selection ends in this node: then select everything + // before our part of the node + const TextFrameIndex nCoreEnd = + pFrame->MapModelToViewPos(*pEnd); + if( nCoreEnd > + GetPortionData().GetLastValidCorePosition() ) + { + // selection extends beyond out part of this para + nLocalEnd = GetPortionData().GetAccessibleString(). + getLength(); + } + else if( nCoreEnd >= + GetPortionData().GetFirstValidCorePosition() ) + { + // selection is inside our part of this para + SAL_WARN_IF( + !GetPortionData().IsValidCorePosition( + nCoreEnd), + "sw.a11y", + "problem determining valid core position"); + + nLocalEnd = GetPortionData().GetAccessiblePosition( + nCoreEnd ); + } + } + + if( ( nLocalStart != -1 ) && ( nLocalEnd != -1 ) ) + { + nStart = nLocalStart; + nEnd = nLocalEnd; + bRet = true; + } + } // if hit the index + else + { + --*pSelection; + } + } + // else: this PaM doesn't point to this paragraph + } + // else: this PaM is collapsed and doesn't select anything + if(bRet) + break; + } + } + // else: nocursor -> no selection + + if (pSelection && bRet) + { + sal_Int32 nCaretPos = GetCaretPos(); + if( nStart == nCaretPos ) + { + sal_Int32 tmp = nStart; + nStart = nEnd; + nEnd = tmp; + } + } + return bRet; +} + +sal_Int16 SAL_CALL SwAccessibleParagraph::getAccessibleRole() +{ + SolarMutexGuard g; + + //Get the real heading level, Heading1 ~ Heading10 + if (m_nHeadingLevel > 0) + { + return AccessibleRole::HEADING; + } + else + { + return AccessibleRole::PARAGRAPH; + } +} + +//Get the real heading level, Heading1 ~ Heading10 +sal_Int32 SwAccessibleParagraph::GetRealHeadingLevel() +{ + uno::Reference< css::beans::XPropertySet > xPortion = CreateUnoPortion( 0, 0 ); + uno::Any styleAny = xPortion->getPropertyValue( "ParaStyleName" ); + OUString sValue; + if (styleAny >>= sValue) + { + sal_Int32 length = sValue.getLength(); + if (length == 9 || length == 10) + { + if (sValue.startsWith("Heading")) + { + std::u16string_view intStr = sValue.subView(8); + sal_Int32 headingLevel = o3tl::toInt32(intStr); + return headingLevel; + } + } + } + return -1; +} + +uno::Any SAL_CALL SwAccessibleParagraph::getExtendedAttributes() +{ + SolarMutexGuard g; + + uno::Any Ret; + OUString strHeading("heading-level:"); + if( m_nHeadingLevel >= 0 ) + strHeading += OUString::number(m_nHeadingLevel); + // tdf#84102: expose the same attribute with the name "level" + strHeading += ";level:"; + if( m_nHeadingLevel >= 0 ) + strHeading += OUString::number(m_nHeadingLevel); + strHeading += ";"; + + Ret <<= strHeading; + + return Ret; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpara.hxx b/sw/source/core/access/accpara.hxx new file mode 100644 index 000000000..ade2df47d --- /dev/null +++ b/sw/source/core/access/accpara.hxx @@ -0,0 +1,399 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPARA_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPARA_HXX + +#include "acccontext.hxx" +#include <com/sun/star/accessibility/AccessibleScrollType.hpp> +#include <com/sun/star/accessibility/XAccessibleEditableText.hpp> +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <com/sun/star/accessibility/XAccessibleHypertext.hpp> +#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp> +#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp> +#include <com/sun/star/accessibility/XAccessibleTextSelection.hpp> +#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp> +#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp> +#include "accselectionhelper.hxx" +#include <unordered_map> +#include <svl/lstner.hxx> + +class SwTextFrame; +class SwPaM; +class SwAccessiblePortionData; +class SwAccessibleHyperTextData; +class SwRangeRedline; +class SwXTextPortion; +class SwParaChangeTrackingInfo; //#i108125# + +namespace com::sun::star { + namespace i18n { struct Boundary; } + namespace accessibility { class XAccessibleHyperlink; } + namespace style { struct TabStop; } +} + +typedef std::unordered_map< OUString, + css::beans::PropertyValue > tAccParaPropValMap; + +class SwAccessibleParagraph : + public SfxListener, + public SwAccessibleContext, + public css::accessibility::XAccessibleEditableText, + public css::accessibility::XAccessibleSelection, + public css::accessibility::XAccessibleHypertext, + public css::accessibility::XAccessibleTextMarkup, + public css::accessibility::XAccessibleMultiLineText, + public css::accessibility::XAccessibleTextAttributes, + public css::accessibility::XAccessibleTextSelection, + public css::accessibility::XAccessibleExtendedAttributes +{ + friend class SwAccessibleHyperlink; + + OUString m_sDesc; // protected by base classes mutex + + // data for this paragraph's text portions; this contains the + // mapping from the core 'model string' to the accessible text + // string. + // pPortionData may be NULL; it should only be accessed through the + // Get/Clear/Has/UpdatePortionData() methods + std::unique_ptr<SwAccessiblePortionData> m_pPortionData; + std::unique_ptr<SwAccessibleHyperTextData> m_pHyperTextData; + + sal_Int32 m_nOldCaretPos; // The 'old' caret pos. It's only valid as long + // as the cursor is inside this object (protected by + // mutex) + + bool m_bIsHeading; // protected by base classes mutex + sal_Int32 m_nHeadingLevel; + + // implementation for XAccessibleSelection + SwAccessibleSelectionHelper m_aSelectionHelper; + + std::unique_ptr<SwParaChangeTrackingInfo> mpParaChangeTrackInfo; // #i108125# + + // XAccessibleComponent + bool m_bLastHasSelection; + + /// get the (accessible) text string (requires frame; check before) + OUString const & GetString(); + + static OUString GetDescription(); + + // get the current care position + sal_Int32 GetCaretPos(); + + // determine the current selection. Fill the values with + // -1 if there is no selection in the this paragraph + // @param pSelection (optional) check only Nth selection in ring + bool GetSelectionAtIndex(sal_Int32 * pSelection, sal_Int32& nStart, sal_Int32& nEnd); + bool GetSelection(sal_Int32& nStart, sal_Int32& nEnd) { + return GetSelectionAtIndex(nullptr, nStart, nEnd); + } + + // helper for GetSelection and getCaretPosition + // #i27301# - add parameter <_bForSelection>, which indicates, + // if the cursor is retrieved for selection or for caret position. + SwPaM* GetCursor( const bool _bForSelection ); + + // for cut/copy/paste: execute a particular slot at the view shell + void ExecuteAtViewShell( sal_uInt16 nSlot ); + + // helper method for get/setAttributes + // (for the special case of (nEndIndex==-1) a single character will + // be selected) + rtl::Reference<SwXTextPortion> CreateUnoPortion( sal_Int32 nStart, sal_Int32 nEnd ); + + // methods for checking the parameter range: + + // does nPos point to a char? + static bool IsValidChar(sal_Int32 nPos, sal_Int32 nLength); + + // does nPos point to a position? (may be behind the last character) + static bool IsValidPosition(sal_Int32 nPos, sal_Int32 nLength); + + // is nBegin...nEnd a valid range? (nEnd points past the last character) + static bool IsValidRange(sal_Int32 nBegin, sal_Int32 nEnd, sal_Int32 nLength); + + // Ensure ordered range (i.e. nBegin is smaller then nEnd) + static void OrderRange(sal_Int32& nBegin, sal_Int32& nEnd) + { + if( nBegin > nEnd ) + { + sal_Int32 nTmp = nBegin; nBegin = nEnd; nEnd = nTmp; + } + } + + const SwRangeRedline* GetRedlineAtIndex(); + OUString GetFieldTypeNameAtIndex(sal_Int32 nIndex); + + // #i63870# + void _getDefaultAttributesImpl( + const css::uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rDefAttrSeq, + const bool bOnlyCharAttrs = false ); + void _getRunAttributesImpl( + const sal_Int32 nIndex, + const css::uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rRunAttrSeq ); + + void _getSupplementalAttributesImpl( + const css::uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rSupplementalAttrSeq ); + + void _correctValues( + const sal_Int32 nIndex, + std::vector< css::beans::PropertyValue >& rValues ); + +public: + bool IsHeading() const; + +protected: + + // Set states for getAccessibleStateSet. + // This derived class additionally sets MULTILINE(1), MULTISELECTABLE(+), + // FOCUSABLE(+) and FOCUSED(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual void InvalidateContent_( bool bVisibleDataFired ) override; + + virtual void InvalidateCursorPos_() override; + virtual void InvalidateFocus_() override; + + virtual ~SwAccessibleParagraph() override; + + // handling of data for the text portions + + // force update of new portion data + /// @throws css::uno::RuntimeException + void UpdatePortionData(); + + // remove the current portion data + void ClearPortionData(); + + // get portion data; update if necessary + /// @throws css::uno::RuntimeException + SwAccessiblePortionData& GetPortionData() + { + if( m_pPortionData == nullptr ) + UpdatePortionData(); + return *m_pPortionData; + } + + //helpers for word boundaries + + bool GetCharBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ); + bool GetWordBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ); + bool GetSentenceBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ); + bool GetLineBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ); + static bool GetParagraphBoundary( css::i18n::Boundary& rBound, + const OUString& rText ); + bool GetAttributeBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ); + bool GetGlyphBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ); + + // get boundaries of word/sentence/etc. for specified text type + // Does all argument checking, and then delegates to helper methods above. + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + bool GetTextBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos, + sal_Int16 aTextType ); + + virtual void Notify(SfxBroadcaster& rBC, const SfxHint& rHint) override; + +public: + + SwAccessibleParagraph( std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwTextFrame& rTextFrame ); + + inline operator css::accessibility::XAccessibleText *(); + + virtual bool HasCursor() override; // required by map to remember that object + + css::uno::Sequence< css::style::TabStop > GetCurrentTabStop( sal_Int32 nIndex ); + virtual sal_Int16 SAL_CALL getAccessibleRole() override; + + // XAccessibleContext + + // Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // Return the parents locale or throw exception if this object has no + // parent yet/anymore. + virtual css::lang::Locale SAL_CALL + getLocale() override; + + // #i27138# - paragraphs are in relation CONTENT_FLOWS_FROM and/or CONTENT_FLOWS_TO + virtual css::uno::Reference< + css::accessibility::XAccessibleRelationSet> SAL_CALL + getAccessibleRelationSet() override; + + // XAccessibleComponent + + virtual void SAL_CALL grabFocus() override; + // #i71385# + virtual sal_Int32 SAL_CALL getForeground() override; + virtual sal_Int32 SAL_CALL getBackground() override; + + // XServiceInfo + + // Returns an identifier for the implementation of this object. + virtual OUString SAL_CALL + getImplementationName() override; + + // Return whether the specified service is supported by this class. + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // Returns a list of all supported services. In this case that is just + // the AccessibleContext service. + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XInterface + + // (XInterface methods need to be implemented to disambiguate + // between those inherited through SwAccessibleContext and + // XAccessibleEditableText). + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) noexcept override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) noexcept override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleText + virtual sal_Int32 SAL_CALL getCaretPosition() override; + virtual sal_Bool SAL_CALL setCaretPosition( sal_Int32 nIndex ) override; + virtual sal_Unicode SAL_CALL getCharacter( sal_Int32 nIndex ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) override; + virtual css::awt::Rectangle SAL_CALL getCharacterBounds( sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getCharacterCount( ) override; + virtual sal_Int32 SAL_CALL getIndexAtPoint( const css::awt::Point& aPoint ) override; + virtual OUString SAL_CALL getSelectedText( ) override; + virtual sal_Int32 SAL_CALL getSelectionStart() override; + virtual sal_Int32 SAL_CALL getSelectionEnd() override; + virtual sal_Bool SAL_CALL setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual OUString SAL_CALL getText( ) override; + virtual OUString SAL_CALL getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual sal_Bool SAL_CALL copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL scrollSubstringTo( sal_Int32 nStartIndex, sal_Int32 nEndIndex, css::accessibility::AccessibleScrollType aScrollType) override; + + // XAccessibleEditableText + virtual sal_Bool SAL_CALL cutText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL pasteText( sal_Int32 nIndex ) override; + virtual sal_Bool SAL_CALL deleteText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL insertText( const OUString& sText, sal_Int32 nIndex ) override; + virtual sal_Bool SAL_CALL replaceText( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const OUString& sReplacement ) override; + virtual sal_Bool SAL_CALL setAttributes( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const css::uno::Sequence< css::beans::PropertyValue >& aAttributeSet ) override; + virtual sal_Bool SAL_CALL setText( const OUString& sText ) override; + + // XAccessibleSelection + virtual void SAL_CALL selectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( + sal_Int32 nChildIndex ) override; + virtual void SAL_CALL clearAccessibleSelection( ) override; + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + // index has to be treated as global child index. + virtual void SAL_CALL deselectAccessibleChild( + sal_Int32 nChildIndex ) override; + + // XAccessibleHypertext + virtual sal_Int32 SAL_CALL getHyperLinkCount() override; + virtual css::uno::Reference< + css::accessibility::XAccessibleHyperlink > + SAL_CALL getHyperLink( sal_Int32 nLinkIndex ) override; + virtual sal_Int32 SAL_CALL getHyperLinkIndex( sal_Int32 nCharIndex ) override; + + // #i71360# + // XAccessibleTextMarkup + virtual sal_Int32 SAL_CALL getTextMarkupCount( sal_Int32 nTextMarkupType ) override; + + virtual css::accessibility::TextSegment SAL_CALL + getTextMarkup( sal_Int32 nTextMarkupIndex, + sal_Int32 nTextMarkupType ) override; + + virtual css::uno::Sequence< css::accessibility::TextSegment > SAL_CALL + getTextMarkupAtIndex( sal_Int32 nCharIndex, + sal_Int32 nTextMarkupType ) override; + + // XAccessibleTextSelection + virtual sal_Bool SAL_CALL scrollToPosition( const css::awt::Point& aPoint, sal_Bool isLeftTop ) override; + virtual sal_Int32 SAL_CALL getSelectedPortionCount( ) override; + virtual sal_Int32 SAL_CALL getSeletedPositionStart( sal_Int32 nSelectedPortionIndex ) override; + virtual sal_Int32 SAL_CALL getSeletedPositionEnd( sal_Int32 nSelectedPortionIndex ) override; + virtual sal_Bool SAL_CALL removeSelection( sal_Int32 selectionIndex ) override; + virtual sal_Int32 SAL_CALL addSelection( sal_Int32 selectionIndex, sal_Int32 startOffset, sal_Int32 endOffset) override; + // XAccessibleExtendedAttributes + virtual css::uno::Any SAL_CALL getExtendedAttributes() override ; + sal_Int32 GetRealHeadingLevel(); + + // #i89175# + // XAccessibleMultiLineText + virtual sal_Int32 SAL_CALL getLineNumberAtIndex( sal_Int32 nIndex ) override; + + virtual css::accessibility::TextSegment SAL_CALL + getTextAtLineNumber( sal_Int32 nLineNo ) override; + + virtual css::accessibility::TextSegment SAL_CALL + getTextAtLineWithCaret() override; + + virtual sal_Int32 SAL_CALL getNumberOfLineWithCaret() override; + + // #i63870# + // XAccessibleTextAttributes + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getDefaultAttributes( const css::uno::Sequence< OUString >& aRequestedAttributes ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getRunAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) override; +}; + +inline SwAccessibleParagraph::operator css::accessibility::XAccessibleText *() +{ + return static_cast< css::accessibility::XAccessibleEditableText * >( this ); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accportions.cxx b/sw/source/core/access/accportions.cxx new file mode 100644 index 000000000..186e61124 --- /dev/null +++ b/sw/source/core/access/accportions.cxx @@ -0,0 +1,752 @@ +/* -*- 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 . + */ + +#include <sal/config.h> + +#include "accportions.hxx" +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <com/sun/star/i18n/Boundary.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <txttypes.hxx> + +// for portion replacement in Special() +#include <viewopt.hxx> + +// for GetWordBoundary(...), GetSentenceBoundary(...): +#include <breakit.hxx> +#include <txtfrm.hxx> + +// for FillSpecialPos(...) +#include <crstate.hxx> + +using namespace ::com::sun::star; + +using i18n::Boundary; + +// 'portion type' for terminating portions +#define POR_TERMINATE PortionType::NONE + +// portion attributes +#define PORATTR_SPECIAL 1 +#define PORATTR_READONLY 2 +#define PORATTR_GRAY 4 +#define PORATTR_TERM 128 + +/// returns the index of the first position whose value is smaller +/// or equal, and whose following value is equal or larger +template<typename T> +static size_t FindBreak(const std::vector<T>& rPositions, T nValue); + +/// like FindBreak, but finds the last equal or larger position +template<typename T> +static size_t FindLastBreak(const std::vector<T>& rPositions, T nValue); + + +SwAccessiblePortionData::SwAccessiblePortionData( + const SwTextFrame *const pTextFrame, + const SwViewOption* pViewOpt ) : + m_pTextFrame(pTextFrame), + m_nViewPosition( 0 ), + m_pViewOptions( pViewOpt ), + m_nBeforePortions( 0 ), + m_bFinished( false ) +{ + OSL_ENSURE( m_pTextFrame != nullptr, "Need SwTextFrame!" ); + + // reserve some space to reduce memory allocations + m_aLineBreaks.reserve( 5 ); + m_ViewPositions.reserve( 10 ); + m_aAccessiblePositions.reserve( 10 ); + + // always include 'first' line-break position + m_aLineBreaks.push_back( 0 ); +} + +SwAccessiblePortionData::~SwAccessiblePortionData() +{ +} + +void SwAccessiblePortionData::Text(TextFrameIndex const nLength, + PortionType nType, sal_Int32 /*nHeight*/, sal_Int32 /*nWidth*/) +{ + OSL_ENSURE((m_nViewPosition + nLength) <= TextFrameIndex(m_pTextFrame->GetText().getLength()), + "portion exceeds model string!" ); + + OSL_ENSURE( !m_bFinished, "We are already done!" ); + + // ignore zero-length portions + if (nLength == TextFrameIndex(0)) + return; + + // store 'old' positions + m_ViewPositions.push_back( m_nViewPosition ); + m_aAccessiblePositions.push_back( m_aBuffer.getLength() ); + + // store portion attributes + sal_uInt8 nAttr = IsGrayPortionType(nType) ? PORATTR_GRAY : 0; + m_aPortionAttrs.push_back( nAttr ); + + // update buffer + nViewPosition + m_aBuffer.append(m_pTextFrame->GetText().subView(sal_Int32(m_nViewPosition), sal_Int32(nLength))); + m_nViewPosition += nLength; +} + +void SwAccessiblePortionData::Special( + TextFrameIndex const nLength, const OUString& rText, PortionType nType, + sal_Int32 /*nHeight*/, sal_Int32 /*nWidth*/, const SwFont* /*pFont*/) +{ + OSL_ENSURE(m_nViewPosition >= TextFrameIndex(0), "illegal position"); + OSL_ENSURE((m_nViewPosition + nLength) <= TextFrameIndex(m_pTextFrame->GetText().getLength()), + "portion exceeds model string!" ); + + OSL_ENSURE( !m_bFinished, "We are already done!" ); + + // construct string with representation; either directly from + // rText, or use resources for special case portions + OUString sDisplay; + switch( nType ) + { + case PortionType::PostIts: + case PortionType::FlyCnt: + sDisplay = OUString(u'\xfffc'); + break; + case PortionType::Field: + case PortionType::Hidden: + case PortionType::Combined: + case PortionType::IsoRef: + // When the field content is empty, input a special character. + if (rText.isEmpty()) + sDisplay = OUString(u'\xfffc'); + else + sDisplay = rText; + m_aFieldPosition.push_back(m_aBuffer.getLength()); + m_aFieldPosition.push_back(m_aBuffer.getLength() + rText.getLength()); + break; + case PortionType::FootnoteNum: + break; + case PortionType::Footnote: + { + sDisplay = rText; + sal_Int32 nStart=m_aBuffer.getLength(); + sal_Int32 nEnd=nStart + rText.getLength(); + m_vecPairPos.emplace_back(nStart,nEnd); + break; + } + break; + case PortionType::Number: + case PortionType::Bullet: + sDisplay = rText + " "; + break; + // There should probably be some special treatment to graphical bullets + case PortionType::GrfNum: + break; + // #i111768# - apply patch from kstribley: + // Include the control characters. + case PortionType::ControlChar: + sDisplay = rText + OUStringChar(m_pTextFrame->GetText()[sal_Int32(m_nViewPosition)]); + break; + case PortionType::Bookmark: + // TODO + break; + default: + sDisplay = rText; + break; + } + + // ignore zero/zero portions (except for terminators) + if ((nLength == TextFrameIndex(0)) && (sDisplay.getLength() == 0) && (nType != POR_TERMINATE)) + return; + + // special treatment for zero length portion at the beginning: + // count as 'before' portion + if ((nLength == TextFrameIndex(0)) && (m_nViewPosition == TextFrameIndex(0))) + m_nBeforePortions++; + + // store the 'old' positions + m_ViewPositions.push_back( m_nViewPosition ); + m_aAccessiblePositions.push_back( m_aBuffer.getLength() ); + + // store portion attributes + sal_uInt8 nAttr = PORATTR_SPECIAL; + if( IsGrayPortionType(nType) ) nAttr |= PORATTR_GRAY; + if (nLength == TextFrameIndex(0)) nAttr |= PORATTR_READONLY; + if( nType == POR_TERMINATE ) nAttr |= PORATTR_TERM; + m_aPortionAttrs.push_back( nAttr ); + + // update buffer + nViewPosition + m_aBuffer.append( sDisplay ); + m_nViewPosition += nLength; +} + +void SwAccessiblePortionData::LineBreak(sal_Int32 /*nWidth*/) +{ + OSL_ENSURE( !m_bFinished, "We are already done!" ); + + m_aLineBreaks.push_back( m_aBuffer.getLength() ); +} + +void SwAccessiblePortionData::Skip(TextFrameIndex const nLength) +{ + OSL_ENSURE( !m_bFinished, "We are already done!" ); + OSL_ENSURE( m_ViewPositions.empty(), "Never Skip() after portions" ); + OSL_ENSURE(nLength <= TextFrameIndex(m_pTextFrame->GetText().getLength()), + "skip exceeds model string!" ); + + m_nViewPosition += nLength; +} + +void SwAccessiblePortionData::Finish() +{ + OSL_ENSURE( !m_bFinished, "We are already done!" ); + + // include terminator values: always include two 'last character' + // markers in the position arrays to make sure we always find one + // position before the end + Special( TextFrameIndex(0), OUString(), POR_TERMINATE ); + Special( TextFrameIndex(0), OUString(), POR_TERMINATE ); + LineBreak(0); + LineBreak(0); + + m_sAccessibleString = m_aBuffer.makeStringAndClear(); + m_bFinished = true; +} + +bool SwAccessiblePortionData::IsPortionAttrSet( + size_t nPortionNo, sal_uInt8 nAttr ) const +{ + OSL_ENSURE( nPortionNo < m_aPortionAttrs.size(), + "Illegal portion number" ); + return (m_aPortionAttrs[nPortionNo] & nAttr) != 0; +} + +bool SwAccessiblePortionData::IsSpecialPortion( size_t nPortionNo ) const +{ + return IsPortionAttrSet(nPortionNo, PORATTR_SPECIAL); +} + +bool SwAccessiblePortionData::IsGrayPortionType( PortionType nType ) const +{ + // gray portions? + // Compare with: inftxt.cxx, SwTextPaintInfo::DrawViewOpt(...) + bool bGray = false; + switch( nType ) + { + case PortionType::Footnote: + case PortionType::IsoRef: + case PortionType::Ref: + case PortionType::QuoVadis: + case PortionType::Number: + case PortionType::Field: + case PortionType::InputField: + case PortionType::IsoTox: + case PortionType::Tox: + case PortionType::Hidden: + bGray = !m_pViewOptions->IsPagePreview() && + !m_pViewOptions->IsReadonly() && SwViewOption::IsFieldShadings(); + break; + case PortionType::Tab: bGray = m_pViewOptions->IsTab(); break; + case PortionType::SoftHyphen: bGray = m_pViewOptions->IsSoftHyph(); break; + case PortionType::Blank: bGray = m_pViewOptions->IsHardBlank(); break; + default: + break; // bGray is false + } + return bGray; +} + +const OUString& SwAccessiblePortionData::GetAccessibleString() const +{ + OSL_ENSURE( m_bFinished, "Shouldn't call this before we are done!" ); + + return m_sAccessibleString; +} + +void SwAccessiblePortionData::GetLineBoundary( + Boundary& rBound, + sal_Int32 nPos ) const +{ + FillBoundary( rBound, m_aLineBreaks, + FindBreak( m_aLineBreaks, nPos ) ); +} + +// #i89175# +sal_Int32 SwAccessiblePortionData::GetLineCount() const +{ + size_t nBreaks = m_aLineBreaks.size(); + // A non-empty paragraph has at least 4 breaks: one for each line3 and + // 3 additional ones. + // An empty paragraph has 3 breaks. + // Less than 3 breaks is an error case. + sal_Int32 nLineCount = ( nBreaks > 3 ) + ? nBreaks - 3 + : ( ( nBreaks == 3 ) ? 1 : 0 ); + return nLineCount; +} + +sal_Int32 SwAccessiblePortionData::GetLineNo( const sal_Int32 nPos ) const +{ + sal_Int32 nLineNo = FindBreak( m_aLineBreaks, nPos ); + + // handling of position after last character + const sal_Int32 nLineCount( GetLineCount() ); + if ( nLineNo >= nLineCount ) + { + nLineNo = nLineCount - 1; + } + + return nLineNo; +} + +void SwAccessiblePortionData::GetBoundaryOfLine( const sal_Int32 nLineNo, + i18n::Boundary& rLineBound ) +{ + FillBoundary( rLineBound, m_aLineBreaks, nLineNo ); +} + +void SwAccessiblePortionData::GetLastLineBoundary( + Boundary& rBound ) const +{ + OSL_ENSURE( m_aLineBreaks.size() >= 2, "need min + max value" ); + + // The last two positions except the two delimiters are the ones + // we are looking for, except for empty paragraphs (nBreaks==3) + size_t nBreaks = m_aLineBreaks.size(); + FillBoundary( rBound, m_aLineBreaks, nBreaks <= 3 ? 0 : nBreaks-4 ); +} + +TextFrameIndex SwAccessiblePortionData::GetCoreViewPosition(sal_Int32 const nPos) const +{ + OSL_ENSURE( nPos >= 0, "illegal position" ); + OSL_ENSURE( nPos <= m_sAccessibleString.getLength(), "illegal position" ); + + // find the portion number + size_t nPortionNo = FindBreak( m_aAccessiblePositions, nPos ); + + // get core view portion size + TextFrameIndex nStartPos = m_ViewPositions[nPortionNo]; + + // if it's a non-special portion, move into the portion, else + // return the portion start + if( ! IsSpecialPortion( nPortionNo ) ) + { + // text portions have to be of the same width + OSL_ENSURE( sal_Int32(m_ViewPositions[nPortionNo+1] - nStartPos) == + ( m_aAccessiblePositions[nPortionNo+1] - + m_aAccessiblePositions[nPortionNo] ), + "accessibility portion disagrees with text model" ); + + nStartPos += TextFrameIndex(nPos - m_aAccessiblePositions[nPortionNo]); + } + // else: return nStartPos unmodified + + OSL_ENSURE(nStartPos >= TextFrameIndex(0), "There's something weird in number of characters of SwTextFrame"); + return nStartPos; +} + +void SwAccessiblePortionData::FillBoundary( + Boundary& rBound, + const AccessiblePositions& rPositions, + size_t nPos ) +{ + rBound.startPos = rPositions[nPos]; + rBound.endPos = rPositions[nPos+1]; +} + +template<typename T> +static size_t FindBreak(const std::vector<T>& rPositions, T const nValue) +{ + OSL_ENSURE( rPositions.size() >= 2, "need min + max value" ); + OSL_ENSURE( rPositions[0] <= nValue, "need min value" ); + OSL_ENSURE( rPositions[rPositions.size()-1] >= nValue, + "need first terminator value" ); + OSL_ENSURE( rPositions[rPositions.size()-2] >= nValue, + "need second terminator value" ); + + size_t nMin = 0; + size_t nMax = rPositions.size()-2; + + // loop until no more than two candidates are left + while( nMin+1 < nMax ) + { + // check loop invariants + OSL_ENSURE( ( (nMin == 0) && (rPositions[nMin] <= nValue) ) || + ( (nMin != 0) && (rPositions[nMin] < nValue) ), + "minvalue not minimal" ); + OSL_ENSURE( nValue <= rPositions[nMax], "max value not maximal" ); + + // get middle (and ensure progress) + size_t nMiddle = (nMin + nMax)/2; + OSL_ENSURE( nMin < nMiddle, "progress?" ); + OSL_ENSURE( nMiddle < nMax, "progress?" ); + + // check array + OSL_ENSURE( rPositions[nMin] <= rPositions[nMiddle], + "garbled positions array" ); + OSL_ENSURE( rPositions[nMiddle] <= rPositions[nMax], + "garbled positions array" ); + + if( nValue > rPositions[nMiddle] ) + nMin = nMiddle; + else + nMax = nMiddle; + } + + // only two are left; we only need to check which one is the winner + OSL_ENSURE( (nMax == nMin) || (nMax == nMin+1), "only two left" ); + if( (rPositions[nMin] < nValue) && (rPositions[nMin+1] <= nValue) ) + nMin = nMin+1; + + // finally, check to see whether the returned value is the 'right' position + OSL_ENSURE( rPositions[nMin] <= nValue, "not smaller or equal" ); + OSL_ENSURE( nValue <= rPositions[nMin+1], "not equal or larger" ); + OSL_ENSURE( (nMin == 0) || (rPositions[nMin-1] <= nValue), + "earlier value should have been returned" ); + + OSL_ENSURE( nMin < rPositions.size()-1, + "shouldn't return last position (due to terminator values)" ); + + return nMin; +} + +template<typename T> +static size_t FindLastBreak(const std::vector<T>& rPositions, T const nValue) +{ + size_t nResult = FindBreak( rPositions, nValue ); + + // skip 'zero-length' portions + // #i70538# consider size of <rPosition> and ignore last entry + while ( nResult < rPositions.size() - 2 && + rPositions[nResult+1] <= nValue ) + { + nResult++; + } + + return nResult; +} + +void SwAccessiblePortionData::GetSentenceBoundary( + Boundary& rBound, + sal_Int32 nPos ) +{ + OSL_ENSURE( nPos >= 0, "illegal position; check before" ); + OSL_ENSURE( nPos < m_sAccessibleString.getLength(), "illegal position" ); + + if( m_pSentences == nullptr ) + { + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + m_pSentences.reset(new AccessiblePositions); + m_pSentences->reserve(10); + + // use xBreak->endOfSentence to iterate over all words; store + // positions in pSentences + sal_Int32 nCurrent = 0; + sal_Int32 nLength = m_sAccessibleString.getLength(); + do + { + m_pSentences->push_back( nCurrent ); + + const TextFrameIndex nFramePos = GetCoreViewPosition(nCurrent); + + sal_Int32 nNew = g_pBreakIt->GetBreakIter()->endOfSentence( + m_sAccessibleString, nCurrent, + g_pBreakIt->GetLocale(m_pTextFrame->GetLangOfChar(nFramePos, 0, true))) + 1; + + if( (nNew < 0) && (nNew > nLength) ) + nNew = nLength; + else if (nNew <= nCurrent) + nNew = nCurrent + 1; // ensure forward progress + + nCurrent = nNew; + } + while (nCurrent < nLength); + + // finish with two terminators + m_pSentences->push_back( nLength ); + m_pSentences->push_back( nLength ); + } + + FillBoundary( rBound, *m_pSentences, FindBreak( *m_pSentences, nPos ) ); +} + +void SwAccessiblePortionData::GetAttributeBoundary( + Boundary& rBound, + sal_Int32 nPos) const +{ + OSL_ENSURE( m_pTextFrame != nullptr, "Need SwTextNode!" ); + + // attribute boundaries can only occur on portion boundaries + FillBoundary( rBound, m_aAccessiblePositions, + FindBreak( m_aAccessiblePositions, nPos ) ); +} + +sal_Int32 SwAccessiblePortionData::GetAccessiblePosition(TextFrameIndex const nPos) const +{ + OSL_ENSURE(nPos <= TextFrameIndex(m_pTextFrame->GetText().getLength()), "illegal position"); + + // find the portion number + // #i70538# - consider "empty" model portions - e.g. number portion + size_t nPortionNo = FindLastBreak( m_ViewPositions, nPos ); + + sal_Int32 nRet = m_aAccessiblePositions[nPortionNo]; + + // if the view portion has more than one position, go into it; + // else return that position + TextFrameIndex nStartPos = m_ViewPositions[nPortionNo]; + TextFrameIndex nEndPos = m_ViewPositions[nPortionNo+1]; + if (!IsSpecialPortion(nPortionNo)) + { + // text portions have to be of the same width + OSL_ENSURE( sal_Int32(nEndPos - nStartPos) == + ( m_aAccessiblePositions[nPortionNo+1] - + m_aAccessiblePositions[nPortionNo] ), + "accessibility portion disagrees with text model" ); + + TextFrameIndex nWithinPortion = nPos - m_ViewPositions[nPortionNo]; + nRet += sal_Int32(nWithinPortion); + } + // else: return nRet unmodified + + OSL_ENSURE( (nRet >= 0) && (nRet <= m_sAccessibleString.getLength()), + "too long!" ); + return nRet; +} + +TextFrameIndex SwAccessiblePortionData::FillSpecialPos( + sal_Int32 nPos, + SwSpecialPos& rPos, + SwSpecialPos*& rpPos ) const +{ + size_t nPortionNo = FindLastBreak( m_aAccessiblePositions, nPos ); + + SwSPExtendRange nExtend(SwSPExtendRange::NONE); + sal_Int32 nRefPos(0); + TextFrameIndex nCorePos(0); + + if( nPortionNo < m_nBeforePortions ) + { + nExtend = SwSPExtendRange::BEFORE; + rpPos = &rPos; + } + else + { + TextFrameIndex nCoreEndPos = m_ViewPositions[nPortionNo+1]; + nCorePos = m_ViewPositions[nPortionNo]; + + // skip backwards over zero-length portions, since GetCharRect() + // counts all model-zero-length portions as belonging to the + // previous portion + size_t nCorePortionNo = nPortionNo; + while (nCorePos == nCoreEndPos) + { + nCorePortionNo--; + nCoreEndPos = nCorePos; + nCorePos = m_ViewPositions[nCorePortionNo]; + + OSL_ENSURE( nCorePos >= TextFrameIndex(0), "Can't happen." ); + OSL_ENSURE( nCorePortionNo >= m_nBeforePortions, "Can't happen." ); + } + OSL_ENSURE( nCorePos != nCoreEndPos, + "portion with core-representation expected" ); + + // if we have anything except plain text, compute nExtend + nRefPos + if (IsSpecialPortion(nCorePortionNo)) + { + // case 1: a non-text portion + // reference position is the first accessibility for our + // core portion + nRefPos = m_aAccessiblePositions[ nCorePortionNo ]; + nExtend = SwSPExtendRange::NONE; + rpPos = &rPos; + } + else if(nPortionNo != nCorePortionNo) + { + // case 2: a multi-character (text!) portion, followed by + // zero-length portions + // reference position is the first character of the next + // portion, and we are 'behind' + nRefPos = m_aAccessiblePositions[ nCorePortionNo+1 ]; + nExtend = SwSPExtendRange::BEHIND; + rpPos = &rPos; + } + else + { + // case 3: regular text portion + OSL_ENSURE( sal_Int32(nCoreEndPos - nCorePos) == + ( m_aAccessiblePositions[nPortionNo+1] - + m_aAccessiblePositions[nPortionNo] ), + "text portion expected" ); + + nCorePos += TextFrameIndex(nPos - m_aAccessiblePositions[nPortionNo]); + rpPos = nullptr; + } + } + if( rpPos != nullptr ) + { + OSL_ENSURE( rpPos == &rPos, "Yes!" ); + OSL_ENSURE( nRefPos <= nPos, "wrong reference" ); + + // get the line number, and adjust nRefPos for the line + // (if necessary) + size_t nRefLine = FindBreak( m_aLineBreaks, nRefPos ); + size_t nMyLine = FindBreak( m_aLineBreaks, nPos ); + sal_uInt16 nLineOffset = o3tl::narrowing<sal_uInt16>( nMyLine - nRefLine ); + if( nLineOffset != 0 ) + nRefPos = m_aLineBreaks[ nMyLine ]; + + // fill char offset and 'special position' + rPos.nCharOfst = nPos - nRefPos; + rPos.nExtendRange = nExtend; + rPos.nLineOfst = nLineOffset; + } + + return nCorePos; +} + +bool SwAccessiblePortionData::FillBoundaryIFDateField( css::i18n::Boundary& rBound, const sal_Int32 nPos ) +{ + if( m_aFieldPosition.size() < 2 ) + return false; + for( size_t i = 0; i < m_aFieldPosition.size() - 1; i += 2 ) + { + if( nPos < m_aFieldPosition[ i + 1 ] && nPos >= m_aFieldPosition[ i ] ) + { + rBound.startPos = m_aFieldPosition[i]; + rBound.endPos = m_aFieldPosition[i + 1]; + return true; + } + } + return false; +} + +void SwAccessiblePortionData::AdjustAndCheck( + sal_Int32 nPos, + size_t& nPortionNo, + TextFrameIndex& rCorePos, + bool& bEdit) const +{ + // find portion and get mode position + nPortionNo = FindBreak( m_aAccessiblePositions, nPos ); + rCorePos = m_ViewPositions[ nPortionNo ]; + + // for special portions, make sure we're on a portion boundary + // for text portions, add the in-portion offset + if( IsSpecialPortion( nPortionNo ) ) + bEdit &= nPos == m_aAccessiblePositions[nPortionNo]; + else + rCorePos += TextFrameIndex(nPos - m_aAccessiblePositions[nPortionNo]); +} + +bool SwAccessiblePortionData::GetEditableRange( + sal_Int32 nStart, sal_Int32 nEnd, + TextFrameIndex& rCoreStart, TextFrameIndex& rCoreEnd) const +{ + bool bIsEditable = true; + + // get start and end portions + size_t nStartPortion, nEndPortion; + AdjustAndCheck( nStart, nStartPortion, rCoreStart, bIsEditable ); + AdjustAndCheck( nEnd, nEndPortion, rCoreEnd, bIsEditable ); + + // iterate over portions, and make sure there is no read-only portion + // in-between + size_t nLastPortion = nEndPortion; + + // don't count last portion if we're in front of a special portion + if( IsSpecialPortion(nLastPortion) ) + { + if (nLastPortion > 0) + nLastPortion--; + else + // special case: because size_t is usually unsigned, we can't just + // decrease nLastPortion to -1 (which would normally do the job, so + // this whole if wouldn't be needed). Instead, we'll do this + // special case and just increase the start portion beyond the last + // portion to make sure the loop below will have zero iteration. + nStartPortion = nLastPortion + 1; + } + + for( size_t nPor = nStartPortion; nPor <= nLastPortion; nPor++ ) + { + bIsEditable &= ! IsPortionAttrSet(nPor, PORATTR_READONLY); + } + + return bIsEditable; +} + +bool SwAccessiblePortionData::IsValidCorePosition(TextFrameIndex const nPos) const +{ + // a position is valid if it's within the core view positions that we know + return (m_ViewPositions[0] <= nPos) && (nPos <= m_ViewPositions.back()); +} + +bool SwAccessiblePortionData::IsZeroCorePositionData() +{ + if (m_ViewPositions.empty()) return true; + return m_ViewPositions[0] == TextFrameIndex(0) + && m_ViewPositions.back() == TextFrameIndex(0); +} + +bool SwAccessiblePortionData::IsIndexInFootnode(sal_Int32 nIndex) +{ + for (const auto & pairPos : m_vecPairPos) + { + if(nIndex >= pairPos.first && nIndex < pairPos.second ) + { + return true; + } + } + return false; +} + +bool SwAccessiblePortionData::IsInGrayPortion( sal_Int32 nPos ) +{ +// return IsGrayPortion( FindBreak( aAccessiblePositions, nPos ) ); + return IsPortionAttrSet( FindBreak( m_aAccessiblePositions, nPos ), + PORATTR_GRAY ); +} + +sal_Int32 SwAccessiblePortionData::GetFieldIndex(sal_Int32 nPos) const +{ + sal_Int32 nIndex = -1; + if( m_aFieldPosition.size() >= 2 ) + { + for( size_t i = 0; i < m_aFieldPosition.size() - 1; i += 2 ) + { + if( nPos <= m_aFieldPosition[ i + 1 ] && nPos >= m_aFieldPosition[ i ] ) + { + nIndex = i/2; + break; + } + } + } + return nIndex; +} + +TextFrameIndex SwAccessiblePortionData::GetFirstValidCorePosition() const +{ + return m_ViewPositions[0]; +} + +TextFrameIndex SwAccessiblePortionData::GetLastValidCorePosition() const +{ + return m_ViewPositions.back(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accportions.hxx b/sw/source/core/access/accportions.hxx new file mode 100644 index 000000000..5598699a6 --- /dev/null +++ b/sw/source/core/access/accportions.hxx @@ -0,0 +1,171 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPORTIONS_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPORTIONS_HXX + +#include <SwPortionHandler.hxx> +#include <sal/types.h> +#include <rtl/ustrbuf.hxx> +#include <memory> +#include <vector> + +class SwTextFrame; +struct SwSpecialPos; +class SwViewOption; +namespace com::sun::star { + namespace i18n { struct Boundary; } +} + +/** + * collect text portion data from the layout through SwPortionHandler interface + */ +class SwAccessiblePortionData : public SwPortionHandler +{ + // the frame this portion is referring to + SwTextFrame const* m_pTextFrame; + + // variables used while collecting the data + OUStringBuffer m_aBuffer; + TextFrameIndex m_nViewPosition; + const SwViewOption* m_pViewOptions; + + /// the accessible string + /// note that the content is different both from the string in the text + /// node(s) as well as the string in the text frame, so there are 3 + /// different index spaces involved. + OUString m_sAccessibleString; + + // positions array + // instances of Position_t must always include the minimum and + // maximum positions as first/last elements (to simplify the + // algorithms) + typedef std::vector<sal_Int32> AccessiblePositions; + typedef std::vector<TextFrameIndex> FramePositions; + + AccessiblePositions m_aLineBreaks; /// position of line breaks + FramePositions m_ViewPositions; /// position of portion breaks in the core view + AccessiblePositions m_aAccessiblePositions; /// portion breaks in m_sAccessibleString + AccessiblePositions m_aFieldPosition; + + std::vector<sal_uInt8> m_aPortionAttrs; /// additional portion attributes + + std::unique_ptr<AccessiblePositions> m_pSentences; /// positions of sentence breaks + + size_t m_nBeforePortions; /// # of portions before first core character + bool m_bFinished; + + /// fill the boundary with the values from rPositions[nPos] + static void FillBoundary(css::i18n::Boundary& rBound, + const AccessiblePositions& rPositions, + size_t nPos ); + + /// Access to portion attributes + bool IsPortionAttrSet( size_t nPortionNo, sal_uInt8 nAttr ) const; + bool IsSpecialPortion( size_t nPortionNo ) const; + bool IsGrayPortionType( PortionType nType ) const; + + // helper method for GetEditableRange(...): + void AdjustAndCheck( sal_Int32 nPos, size_t& nPortionNo, + TextFrameIndex& rCorePos, bool& bEdit) const; + +public: + SwAccessiblePortionData( const SwTextFrame* pTextFrame, + const SwViewOption* pViewOpt ); + virtual ~SwAccessiblePortionData() override; + + // SwPortionHandler methods + virtual void Text(TextFrameIndex nLength, PortionType nType, sal_Int32 nHeight = 0, sal_Int32 nWidth = 0) override; + virtual void Special(TextFrameIndex nLength, const OUString& rText, PortionType nType, sal_Int32 nHeight = 0, sal_Int32 nWidth = 0, const SwFont* pFont = nullptr) override; + virtual void LineBreak(sal_Int32 nWidth) override; + virtual void Skip(TextFrameIndex nLength) override; + virtual void Finish() override; + + bool FillBoundaryIFDateField( css::i18n::Boundary& rBound, const sal_Int32 nPos ); + bool IsIndexInFootnode(sal_Int32 nIndex); + bool IsInGrayPortion( sal_Int32 nPos ); + sal_Int32 GetFieldIndex(sal_Int32 nPos) const; + + bool IsZeroCorePositionData(); + + // access to the portion data + + /// get the text string, as presented by the layout + const OUString& GetAccessibleString() const; + + /// get the start & end positions of the sentence + void GetLineBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ) const; + + // get start and end position of the last line + void GetLastLineBoundary( css::i18n::Boundary& rBound ) const; + + /// Determine whether this core position is valid for these portions. + /// (A paragraph may be split into several frames, e.g. at page + /// boundaries. In this case, only part of a paragraph is represented + /// through this object. This method determines whether one particular + /// position is valid for this object or not.) + bool IsValidCorePosition(TextFrameIndex nPos) const; + TextFrameIndex GetFirstValidCorePosition() const; + TextFrameIndex GetLastValidCorePosition() const; + + /// get the position in the accessibility string for a given view position + sal_Int32 GetAccessiblePosition(TextFrameIndex nPos) const; + + // #i89175# + sal_Int32 GetLineCount() const; + sal_Int32 GetLineNo( const sal_Int32 nPos ) const; + void GetBoundaryOfLine( const sal_Int32 nLineNo, + css::i18n::Boundary& rLineBound ); + + /// get the position in the core view string for a given + /// (accessibility) position + TextFrameIndex GetCoreViewPosition(sal_Int32 nPos) const; + + /// fill a SwSpecialPos structure, suitable for calling + /// SwTextFrame->GetCharRect + /// Returns the core position, and fills rpPos either with NULL or + /// with the &rPos, after putting the appropriate data into it. + TextFrameIndex FillSpecialPos(sal_Int32 nPos, + SwSpecialPos& rPos, + SwSpecialPos*& rpPos ) const; + + // get boundaries of words/sentences. The data structures are + // created on-demand. + void GetSentenceBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ); + + // get (a) boundary for attribute change + void GetAttributeBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ) const; + + /// Convert start and end positions into core positions. + /// @returns true if 'special' portions are included either completely + /// or not at all. This can be used to test whether editing + /// that range would be legal + bool GetEditableRange( sal_Int32 nStart, sal_Int32 nEnd, + TextFrameIndex& rCoreStart, TextFrameIndex& rCoreEnd) const; + +private: + std::vector< std::pair<sal_Int32,sal_Int32> > m_vecPairPos; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpreview.cxx b/sw/source/core/access/accpreview.cxx new file mode 100644 index 000000000..503cc543e --- /dev/null +++ b/sw/source/core/access/accpreview.cxx @@ -0,0 +1,76 @@ +/* -*- 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 . + */ + +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <strings.hrc> +#include "accpreview.hxx" + +constexpr OUStringLiteral sImplementationName + = u"com.sun.star.comp.Writer.SwAccessibleDocumentPageView"; + +using ::com::sun::star::uno::Sequence; + +SwAccessiblePreview::SwAccessiblePreview(std::shared_ptr<SwAccessibleMap> const& pMap) + : SwAccessibleDocumentBase(pMap) +{ + SetName( GetResource( STR_ACCESS_PREVIEW_DOC_NAME ) ); +} + +SwAccessiblePreview::~SwAccessiblePreview() +{ +} + +OUString SwAccessiblePreview::getImplementationName( ) +{ + return sImplementationName; +} + +sal_Bool SwAccessiblePreview::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence<OUString> SwAccessiblePreview::getSupportedServiceNames( ) +{ + return {"com.sun.star.text.AccessibleTextDocumentPageView", + sAccessibleServiceName}; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessiblePreview::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +OUString SAL_CALL SwAccessiblePreview::getAccessibleDescription() +{ + return GetResource( STR_ACCESS_PREVIEW_DOC_NAME ); +} + +OUString SAL_CALL SwAccessiblePreview::getAccessibleName() +{ + return SwAccessibleDocumentBase::getAccessibleName() + " " + GetResource( STR_ACCESS_PREVIEW_DOC_SUFFIX ); +} + +void SwAccessiblePreview::InvalidateFocus_() +{ + FireStateChangedEvent( css::accessibility::AccessibleStateType::FOCUSED, true ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpreview.hxx b/sw/source/core/access/accpreview.hxx new file mode 100644 index 000000000..a60254f6a --- /dev/null +++ b/sw/source/core/access/accpreview.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPREVIEW_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPREVIEW_HXX + +#include "accdoc.hxx" + +/** + * accessibility implementation for the page preview. + * The children of the page preview are the pages that are visible in the + * preview. + * + * The vast majority of the implementation logic is inherited from + * SwAccessibleDocumentBase. + */ +class SwAccessiblePreview : public SwAccessibleDocumentBase +{ + virtual ~SwAccessiblePreview() override; + +public: + SwAccessiblePreview(std::shared_ptr<SwAccessibleMap> const& pMap); + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. + */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. + */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. + */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + OUString SAL_CALL getAccessibleDescription() override; + OUString SAL_CALL getAccessibleName() override; + virtual void InvalidateFocus_() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accselectionhelper.cxx b/sw/source/core/access/accselectionhelper.cxx new file mode 100644 index 000000000..96a622ece --- /dev/null +++ b/sw/source/core/access/accselectionhelper.cxx @@ -0,0 +1,341 @@ +/* -*- 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 . + */ + +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include "accselectionhelper.hxx" + +#include "acccontext.hxx" +#include <accmap.hxx> +#include <o3tl/safeint.hxx> +#include <svx/AccessibleShape.hxx> +#include <viewsh.hxx> +#include <fesh.hxx> +#include <vcl/svapp.hxx> +#include <flyfrm.hxx> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/XAccessibleStateSet.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <fmtanchr.hxx> + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +using ::com::sun::star::accessibility::XAccessible; +using ::com::sun::star::accessibility::XAccessibleContext; +using ::com::sun::star::accessibility::XAccessibleSelection; + +using namespace ::sw::access; + +SwAccessibleSelectionHelper::SwAccessibleSelectionHelper( + SwAccessibleContext& rContext ) : + m_rContext( rContext ) +{ +} + +SwFEShell* SwAccessibleSelectionHelper::GetFEShell() +{ + OSL_ENSURE( m_rContext.GetMap() != nullptr, "no map?" ); + SwViewShell* pViewShell = m_rContext.GetMap()->GetShell(); + OSL_ENSURE( pViewShell != nullptr, + "No view shell? Then what are you looking at?" ); + + SwFEShell* pFEShell = dynamic_cast<SwFEShell*>( pViewShell ); + + return pFEShell; +} + +void SwAccessibleSelectionHelper::throwIndexOutOfBoundsException() +{ + Reference < XAccessibleContext > xThis( &m_rContext ); + Reference < XAccessibleSelection >xSelThis( xThis, UNO_QUERY ); + lang::IndexOutOfBoundsException aExcept( + "index out of bounds", + xSelThis ); + throw aExcept; +} + +// XAccessibleSelection +void SwAccessibleSelectionHelper::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + // Get the respective child as SwFrame (also do index checking), ... + const SwAccessibleChild aChild = m_rContext.GetChild( *(m_rContext.GetMap()), + nChildIndex ); + if( !aChild.IsValid() ) + throwIndexOutOfBoundsException(); + + // we can only select fly frames, so we ignore (should: return + // false) all other attempts at child selection + if (GetFEShell()) + { + const SdrObject *pObj = aChild.GetDrawObject(); + if( pObj ) + m_rContext.Select( const_cast< SdrObject *>( pObj ), nullptr==aChild.GetSwFrame()); + } + // no frame shell, or no frame, or no fly frame -> can't select +} + +//When the selected state of the SwFrameOrObj is set, return true. +static bool lcl_getSelectedState(const SwAccessibleChild& aChild, + SwAccessibleContext* pContext, + SwAccessibleMap* pMap) +{ + Reference< XAccessible > xAcc; + if ( aChild.GetSwFrame() ) + { + xAcc = pMap->GetContext( aChild.GetSwFrame(), false ); + } + else if ( aChild.GetDrawObject() ) + { + xAcc = pMap->GetContext( aChild.GetDrawObject(), pContext, false ); + } + + if( xAcc.is() ) + { + Reference< XAccessibleContext > pRContext = xAcc->getAccessibleContext(); + if(!pRContext.is()) + return false; + Reference<XAccessibleStateSet> pRStateSet = pRContext->getAccessibleStateSet(); + if( pRStateSet.is() ) + { + const Sequence<short> aStates = pRStateSet->getStates(); + if (std::find(aStates.begin(), aStates.end(), AccessibleStateType::SELECTED) != aStates.end()) + return true; + } + } + return false; +} + +bool SwAccessibleSelectionHelper::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + // Get the respective child as SwFrame (also do index checking), ... + const SwAccessibleChild aChild = m_rContext.GetChild( *(m_rContext.GetMap()), + nChildIndex ); + if( !aChild.IsValid() ) + throwIndexOutOfBoundsException(); + + // ... and compare to the currently selected frame + bool bRet = false; + if (const SwFEShell* pFEShell = GetFEShell()) + { + if ( aChild.GetSwFrame() != nullptr ) + { + bRet = (pFEShell->GetSelectedFlyFrame() == aChild.GetSwFrame()); + } + else if ( aChild.GetDrawObject() ) + { + bRet = pFEShell->IsObjSelected( *aChild.GetDrawObject() ); + } + //If the SwFrameOrObj is not selected directly in the UI, we should check whether it is selected in the selection cursor. + if( !bRet ) + { + if( lcl_getSelectedState( aChild, &m_rContext, m_rContext.GetMap() ) ) + bRet = true; + } + } + + return bRet; +} + +void SwAccessibleSelectionHelper::selectAllAccessibleChildren( ) +{ + SolarMutexGuard aGuard; + + // We can select only one. So iterate over the children to find + // the first we can select, and select it. + + SwFEShell* pFEShell = GetFEShell(); + if (!pFEShell) + return; + + std::list< SwAccessibleChild > aChildren; + m_rContext.GetChildren( *(m_rContext.GetMap()), aChildren ); + + for( const SwAccessibleChild& rChild : aChildren ) + { + const SdrObject* pObj = rChild.GetDrawObject(); + const SwFrame* pFrame = rChild.GetSwFrame(); + if( pObj && !(pFrame != nullptr && pFEShell->IsObjSelected()) ) + { + m_rContext.Select( const_cast< SdrObject *>( pObj ), nullptr==pFrame ); + if( pFrame ) + break; + } + } +} + +sal_Int32 SwAccessibleSelectionHelper::getSelectedAccessibleChildCount( ) +{ + SolarMutexGuard aGuard; + + sal_Int32 nCount = 0; + // Only one frame can be selected at a time, and we only frames + // for selectable children. + if (const SwFEShell* pFEShell = GetFEShell()) + { + const SwFlyFrame* pFlyFrame = pFEShell->GetSelectedFlyFrame(); + if( pFlyFrame ) + { + nCount = 1; + } + else + { + const size_t nSelObjs = pFEShell->IsObjSelected(); + if( nSelObjs > 0 ) + { + std::list< SwAccessibleChild > aChildren; + m_rContext.GetChildren( *(m_rContext.GetMap()), aChildren ); + + for( const SwAccessibleChild& rChild : aChildren ) + { + if( rChild.GetDrawObject() && !rChild.GetSwFrame() && + SwAccessibleFrame::GetParent(rChild, m_rContext.IsInPagePreview()) + == m_rContext.GetFrame() && + pFEShell->IsObjSelected( *rChild.GetDrawObject() ) ) + { + nCount++; + } + if (o3tl::make_unsigned(nCount) >= nSelObjs) + break; + } + } + } + //If the SwFrameOrObj is not selected directly in the UI, + //we should check whether it is selected in the selection cursor. + if( nCount == 0 ) + { + std::list< SwAccessibleChild > aChildren; + m_rContext.GetChildren( *(m_rContext.GetMap()), aChildren ); + nCount = static_cast<sal_Int32>(std::count_if(aChildren.begin(), aChildren.end(), + [this](const SwAccessibleChild& aChild) { return lcl_getSelectedState(aChild, &m_rContext, m_rContext.GetMap()); })); + } + } + return nCount; +} + +Reference<XAccessible> SwAccessibleSelectionHelper::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + SolarMutexGuard aGuard; + + // Since the index is relative to the selected children, and since + // there can be at most one selected frame child, the index must + // be 0, and a selection must exist, otherwise we have to throw an + // lang::IndexOutOfBoundsException + SwFEShell* pFEShell = GetFEShell(); + if (!pFEShell) + throwIndexOutOfBoundsException(); + + SwAccessibleChild aChild; + const SwFlyFrame *pFlyFrame = pFEShell->GetSelectedFlyFrame(); + if( pFlyFrame ) + { + if( 0 == nSelectedChildIndex ) + { + if(SwAccessibleFrame::GetParent( SwAccessibleChild(pFlyFrame), m_rContext.IsInPagePreview()) == m_rContext.GetFrame() ) + { + aChild = pFlyFrame; + } + else + { + const SwFrameFormat *pFrameFormat = pFlyFrame->GetFormat(); + if (pFrameFormat) + { + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + const SwFrame *pParaFrame = SwAccessibleFrame::GetParent( SwAccessibleChild(pFlyFrame), m_rContext.IsInPagePreview() ); + aChild = pParaFrame; + } + } + } + } + } + else + { + const size_t nSelObjs = pFEShell->IsObjSelected(); + if( 0 == nSelObjs || o3tl::make_unsigned(nSelectedChildIndex) >= nSelObjs ) + throwIndexOutOfBoundsException(); + + std::list< SwAccessibleChild > aChildren; + m_rContext.GetChildren( *(m_rContext.GetMap()), aChildren ); + + for( const SwAccessibleChild& rChild : aChildren ) + { + if( rChild.GetDrawObject() && !rChild.GetSwFrame() && + SwAccessibleFrame::GetParent(rChild, m_rContext.IsInPagePreview()) == + m_rContext.GetFrame() && + pFEShell->IsObjSelected( *rChild.GetDrawObject() ) ) + { + if( 0 == nSelectedChildIndex ) + aChild = rChild; + else + --nSelectedChildIndex; + } + if (aChild.IsValid()) + break; + } + } + + if( !aChild.IsValid() ) + throwIndexOutOfBoundsException(); + + OSL_ENSURE( m_rContext.GetMap() != nullptr, "We need the map." ); + Reference< XAccessible > xChild; + if( aChild.GetSwFrame() ) + { + ::rtl::Reference < SwAccessibleContext > xChildImpl( + m_rContext.GetMap()->GetContextImpl( aChild.GetSwFrame() ) ); + if( xChildImpl.is() ) + { + xChildImpl->SetParent( &m_rContext ); + xChild = xChildImpl.get(); + } + } + else if ( aChild.GetDrawObject() ) + { + ::rtl::Reference < ::accessibility::AccessibleShape > xChildImpl( + m_rContext.GetMap()->GetContextImpl( aChild.GetDrawObject(), + &m_rContext ) ); + if( xChildImpl.is() ) + xChild = xChildImpl.get(); + } + return xChild; +} + +// index has to be treated as global child index. +void SwAccessibleSelectionHelper::deselectAccessibleChild( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard g; + + if( nChildIndex < 0 || + nChildIndex >= m_rContext.GetChildCount( *(m_rContext.GetMap()) ) ) + throwIndexOutOfBoundsException(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accselectionhelper.hxx b/sw/source/core/access/accselectionhelper.hxx new file mode 100644 index 000000000..ceaa2a784 --- /dev/null +++ b/sw/source/core/access/accselectionhelper.hxx @@ -0,0 +1,73 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCSELECTIONHELPER_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCSELECTIONHELPER_HXX + +#include <sal/types.h> +#include <com/sun/star/uno/Reference.h> + +namespace com::sun::star::accessibility { class XAccessible; } + +class SwAccessibleContext; +class SwFEShell; + +class SwAccessibleSelectionHelper +{ + /// the context on which this helper works + SwAccessibleContext& m_rContext; + + /// get FE-Shell + SwFEShell* GetFEShell(); + + /// @throws css::lang::IndexOutOfBoundsException + void throwIndexOutOfBoundsException(); + +public: + SwAccessibleSelectionHelper( SwAccessibleContext& rContext ); + + // XAccessibleSelection + + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::uno::RuntimeException + void selectAccessibleChild( + sal_Int32 nChildIndex ); + + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::uno::RuntimeException + bool isAccessibleChildSelected( + sal_Int32 nChildIndex ); + /// @throws css::uno::RuntimeException + void selectAllAccessibleChildren( ); + /// @throws css::uno::RuntimeException + sal_Int32 getSelectedAccessibleChildCount( ); + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::accessibility::XAccessible > getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ); + // index has to be treated as global child index. + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::uno::RuntimeException + void deselectAccessibleChild( + sal_Int32 nChildIndex ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acctable.cxx b/sw/source/core/access/acctable.cxx new file mode 100644 index 000000000..0fd373754 --- /dev/null +++ b/sw/source/core/access/acctable.cxx @@ -0,0 +1,1739 @@ +/* -*- 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 . + */ + +#include <sal/log.hxx> + +#include <algorithm> +#include <vector> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <o3tl/safeint.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <vcl/svapp.hxx> +#include <frmfmt.hxx> +#include <tabfrm.hxx> +#include <cellfrm.hxx> +#include <swtable.hxx> +#include <crsrsh.hxx> +#include <viscrs.hxx> +#include "accfrmobjslist.hxx" +#include <accmap.hxx> +#include <strings.hrc> +#include "acctable.hxx" + +#include <com/sun/star/accessibility/XAccessibleText.hpp> + +#include <editeng/brushitem.hxx> +#include <swatrset.hxx> +#include <frmatr.hxx> + +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::sw::access; + +typedef o3tl::sorted_vector< sal_Int32 > Int32Set_Impl; + +const unsigned int SELECTION_WITH_NUM = 10; + +namespace { + +class SwAccTableSelHandler_Impl +{ +public: + virtual void Unselect( sal_Int32 nRowOrCol, sal_Int32 nExt ) = 0; + +protected: + ~SwAccTableSelHandler_Impl() {} +}; + +} + +class SwAccessibleTableData_Impl +{ + SwAccessibleMap& mrAccMap; + Int32Set_Impl maRows; + Int32Set_Impl maColumns; + Point maTabFramePos; + const SwTabFrame *mpTabFrame; + bool mbIsInPagePreview; + bool mbOnlyTableColumnHeader; + + void CollectData( const SwFrame *pFrame ); + + bool FindCell( const Point& rPos, const SwFrame *pFrame , + bool bExact, const SwFrame *& rFrame ) const; + + void GetSelection( const Point& rTabPos, const SwRect& rArea, + const SwSelBoxes& rSelBoxes, const SwFrame *pFrame, + SwAccTableSelHandler_Impl& rSelHdl, + bool bColumns ) const; + + // #i77106# + bool IncludeRow( const SwFrame& rFrame ) const + { + return !mbOnlyTableColumnHeader || + mpTabFrame->IsInHeadline( rFrame ); + } +public: + // #i77106# - add third optional parameter <bOnlyTableColumnHeader>, default value <false> + SwAccessibleTableData_Impl( SwAccessibleMap& rAccMap, + const SwTabFrame *pTabFrame, + bool bIsInPagePreview, + bool bOnlyTableColumnHeader = false ); + + const Int32Set_Impl& GetRows() const { return maRows; } + const Int32Set_Impl& GetColumns() const { return maColumns; } + + inline Int32Set_Impl::const_iterator GetRowIter( sal_Int32 nRow ) const; + inline Int32Set_Impl::const_iterator GetColumnIter( sal_Int32 nCol ) const; + + /// @throws lang::IndexOutOfBoundsException + /// @throws uno::RuntimeException + const SwFrame *GetCell( sal_Int32 nRow, sal_Int32 nColumn, SwAccessibleTable *pThis ) const; + const SwFrame *GetCellAtPos( sal_Int32 nLeft, sal_Int32 nTop ) const; + inline sal_Int32 GetRowCount() const; + inline sal_Int32 GetColumnCount() const; + bool CompareExtents( const SwAccessibleTableData_Impl& r ) const; + + void GetSelection( sal_Int32 nStart, sal_Int32 nEnd, + const SwSelBoxes& rSelBoxes, + SwAccTableSelHandler_Impl& rSelHdl, + bool bColumns ) const; + + /// @throws lang::IndexOutOfBoundsException + void CheckRowAndCol( sal_Int32 nRow, sal_Int32 nCol, + SwAccessibleTable *pThis ) const; + + const Point& GetTablePos() const { return maTabFramePos; } + void SetTablePos( const Point& rPos ) { maTabFramePos = rPos; } +}; + +void SwAccessibleTableData_Impl::CollectData( const SwFrame *pFrame ) +{ + const SwAccessibleChildSList aList( *pFrame, mrAccMap ); + SwAccessibleChildSList::const_iterator aIter( aList.begin() ); + SwAccessibleChildSList::const_iterator aEndIter( aList.end() ); + while( aIter != aEndIter ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame *pLower = rLower.GetSwFrame(); + if( pLower ) + { + if( pLower->IsRowFrame() ) + { + // #i77106# + if ( IncludeRow( *pLower ) ) + { + maRows.insert( pLower->getFrameArea().Top() - maTabFramePos.getY() ); + CollectData( pLower ); + } + } + else if( pLower->IsCellFrame() && + rLower.IsAccessible( mbIsInPagePreview ) ) + { + maColumns.insert( pLower->getFrameArea().Left() - maTabFramePos.getX() ); + } + else + { + CollectData( pLower ); + } + } + ++aIter; + } +} + +bool SwAccessibleTableData_Impl::FindCell( + const Point& rPos, const SwFrame *pFrame, bool bExact, + const SwFrame *& rRet ) const +{ + bool bFound = false; + + const SwAccessibleChildSList aList( *pFrame, mrAccMap ); + SwAccessibleChildSList::const_iterator aIter( aList.begin() ); + SwAccessibleChildSList::const_iterator aEndIter( aList.end() ); + while( !bFound && aIter != aEndIter ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame *pLower = rLower.GetSwFrame(); + OSL_ENSURE( pLower, "child should be a frame" ); + if( pLower ) + { + if( rLower.IsAccessible( mbIsInPagePreview ) ) + { + OSL_ENSURE( pLower->IsCellFrame(), "lower is not a cell frame" ); + const SwRect& rFrame = pLower->getFrameArea(); + if( rFrame.Right() >= rPos.X() && rFrame.Bottom() >= rPos.Y() ) + { + // We have found the cell + OSL_ENSURE( rFrame.Left() <= rPos.X() && rFrame.Top() <= rPos.Y(), + "find frame moved to far!" ); + bFound = true; + if( !bExact || + (rFrame.Top() == rPos.Y() && rFrame.Left() == rPos.Y() ) ) + { + rRet = pLower; + } + } + } + else + { + // #i77106# + if ( !pLower->IsRowFrame() || + IncludeRow( *pLower ) ) + { + bFound = FindCell( rPos, pLower, bExact, rRet ); + } + } + } + ++aIter; + } + + return bFound; +} + +void SwAccessibleTableData_Impl::GetSelection( + const Point& rTabPos, + const SwRect& rArea, + const SwSelBoxes& rSelBoxes, + const SwFrame *pFrame, + SwAccTableSelHandler_Impl& rSelHdl, + bool bColumns ) const +{ + const SwAccessibleChildSList aList( *pFrame, mrAccMap ); + SwAccessibleChildSList::const_iterator aIter( aList.begin() ); + SwAccessibleChildSList::const_iterator aEndIter( aList.end() ); + while( aIter != aEndIter ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame *pLower = rLower.GetSwFrame(); + OSL_ENSURE( pLower, "child should be a frame" ); + const SwRect& rBox = rLower.GetBox( mrAccMap ); + if( pLower && rBox.Overlaps( rArea ) ) + { + if( rLower.IsAccessible( mbIsInPagePreview ) ) + { + OSL_ENSURE( pLower->IsCellFrame(), "lower is not a cell frame" ); + const SwCellFrame *pCFrame = + static_cast < const SwCellFrame * >( pLower ); + SwTableBox *pBox = + const_cast< SwTableBox *>( pCFrame->GetTabBox() ); + if( rSelBoxes.find( pBox ) == rSelBoxes.end() ) + { + const Int32Set_Impl rRowsOrCols = + bColumns ? maColumns : maRows; + + sal_Int32 nPos = bColumns ? (rBox.Left() - rTabPos.X()) + : (rBox.Top() - rTabPos.Y()); + Int32Set_Impl::const_iterator aSttRowOrCol( + rRowsOrCols.lower_bound( nPos ) ); + sal_Int32 nRowOrCol = + static_cast< sal_Int32 >( std::distance( + rRowsOrCols.begin(), aSttRowOrCol ) ); + + nPos = bColumns ? (rBox.Right() - rTabPos.X()) + : (rBox.Bottom() - rTabPos.Y()); + Int32Set_Impl::const_iterator aEndRowOrCol( + rRowsOrCols.upper_bound( nPos ) ); + sal_Int32 nExt = + static_cast< sal_Int32 >( std::distance( + aSttRowOrCol, aEndRowOrCol ) ); + + rSelHdl.Unselect( nRowOrCol, nExt ); + } + } + else + { + // #i77106# + if ( !pLower->IsRowFrame() || + IncludeRow( *pLower ) ) + { + GetSelection( rTabPos, rArea, rSelBoxes, pLower, rSelHdl, + bColumns ); + } + } + } + ++aIter; + } +} + +const SwFrame *SwAccessibleTableData_Impl::GetCell( + sal_Int32 nRow, sal_Int32 nColumn, + SwAccessibleTable *pThis ) const +{ + CheckRowAndCol( nRow, nColumn, pThis ); + + Int32Set_Impl::const_iterator aSttCol( GetColumnIter( nColumn ) ); + Int32Set_Impl::const_iterator aSttRow( GetRowIter( nRow ) ); + const SwFrame *pCellFrame = GetCellAtPos( *aSttCol, *aSttRow ); + + return pCellFrame; +} + +void SwAccessibleTableData_Impl::GetSelection( + sal_Int32 nStart, sal_Int32 nEnd, + const SwSelBoxes& rSelBoxes, + SwAccTableSelHandler_Impl& rSelHdl, + bool bColumns ) const +{ + SwRect aArea( mpTabFrame->getFrameArea() ); + Point aPos( aArea.Pos() ); + + const Int32Set_Impl& rRowsOrColumns = bColumns ? maColumns : maRows; + if( nStart > 0 ) + { + Int32Set_Impl::const_iterator aStt( rRowsOrColumns.begin() ); + std::advance( aStt, + static_cast< Int32Set_Impl::difference_type >( nStart ) ); + if( bColumns ) + aArea.Left( *aStt + aPos.getX() ); + else + aArea.Top( *aStt + aPos.getY() ); + } + if( nEnd < static_cast< sal_Int32 >( rRowsOrColumns.size() ) ) + { + Int32Set_Impl::const_iterator aEnd( rRowsOrColumns.begin() ); + std::advance( aEnd, + static_cast< Int32Set_Impl::difference_type >( nEnd ) ); + if( bColumns ) + aArea.Right( *aEnd + aPos.getX() - 1 ); + else + aArea.Bottom( *aEnd + aPos.getY() - 1 ); + } + + GetSelection( aPos, aArea, rSelBoxes, mpTabFrame, rSelHdl, bColumns ); +} + +const SwFrame *SwAccessibleTableData_Impl::GetCellAtPos( + sal_Int32 nLeft, sal_Int32 nTop ) const +{ + Point aPos( mpTabFrame->getFrameArea().Pos() ); + aPos.Move( nLeft, nTop ); + const SwFrame *pRet = nullptr; + FindCell( aPos, mpTabFrame, false/*bExact*/, pRet ); + + return pRet; +} + +inline sal_Int32 SwAccessibleTableData_Impl::GetRowCount() const +{ + sal_Int32 count = static_cast< sal_Int32 >( maRows.size() ) ; + count = (count <=0)? 1:count; + return count; +} + +inline sal_Int32 SwAccessibleTableData_Impl::GetColumnCount() const +{ + return static_cast< sal_Int32 >( maColumns.size() ); +} + +bool SwAccessibleTableData_Impl::CompareExtents( + const SwAccessibleTableData_Impl& rCmp ) const +{ + return maRows == rCmp.maRows + && maColumns == rCmp.maColumns; +} + +SwAccessibleTableData_Impl::SwAccessibleTableData_Impl( SwAccessibleMap& rAccMap, + const SwTabFrame *pTabFrame, + bool bIsInPagePreview, + bool bOnlyTableColumnHeader ) + : mrAccMap( rAccMap ) + , maTabFramePos( pTabFrame->getFrameArea().Pos() ) + , mpTabFrame( pTabFrame ) + , mbIsInPagePreview( bIsInPagePreview ) + , mbOnlyTableColumnHeader( bOnlyTableColumnHeader ) +{ + CollectData( mpTabFrame ); +} + +inline Int32Set_Impl::const_iterator SwAccessibleTableData_Impl::GetRowIter( + sal_Int32 nRow ) const +{ + Int32Set_Impl::const_iterator aCol( GetRows().begin() ); + if( nRow > 0 ) + { + std::advance( aCol, + static_cast< Int32Set_Impl::difference_type >( nRow ) ); + } + return aCol; +} + +inline Int32Set_Impl::const_iterator SwAccessibleTableData_Impl::GetColumnIter( + sal_Int32 nColumn ) const +{ + Int32Set_Impl::const_iterator aCol = GetColumns().begin(); + if( nColumn > 0 ) + { + std::advance( aCol, + static_cast< Int32Set_Impl::difference_type >( nColumn ) ); + } + return aCol; +} + +void SwAccessibleTableData_Impl::CheckRowAndCol( + sal_Int32 nRow, sal_Int32 nCol, SwAccessibleTable *pThis ) const +{ + if( ( nRow < 0 || o3tl::make_unsigned(nRow) >= maRows.size() ) || + ( nCol < 0 || o3tl::make_unsigned(nCol) >= maColumns.size() ) ) + { + uno::Reference < XAccessibleTable > xThis( pThis ); + lang::IndexOutOfBoundsException aExcept( + "row or column index out of range", + xThis ); + throw aExcept; + } +} + +namespace { + +class SwAccSingleTableSelHandler_Impl : public SwAccTableSelHandler_Impl +{ + bool m_bSelected; + +public: + + inline SwAccSingleTableSelHandler_Impl(); + + virtual ~SwAccSingleTableSelHandler_Impl() {} + + bool IsSelected() const { return m_bSelected; } + + virtual void Unselect( sal_Int32, sal_Int32 ) override; +}; + +} + +inline SwAccSingleTableSelHandler_Impl::SwAccSingleTableSelHandler_Impl() : + m_bSelected( true ) +{ +} + +void SwAccSingleTableSelHandler_Impl::Unselect( sal_Int32, sal_Int32 ) +{ + m_bSelected = false; +} + +namespace { + +class SwAccAllTableSelHandler_Impl : public SwAccTableSelHandler_Impl + +{ + std::vector< bool > m_aSelected; + sal_Int32 m_nCount; + +public: + explicit SwAccAllTableSelHandler_Impl(sal_Int32 nSize) + : m_aSelected(nSize, true) + , m_nCount(nSize) + { + } + + uno::Sequence < sal_Int32 > GetSelSequence(); + + virtual void Unselect( sal_Int32 nRowOrCol, sal_Int32 nExt ) override; + virtual ~SwAccAllTableSelHandler_Impl(); +}; + +} + +SwAccAllTableSelHandler_Impl::~SwAccAllTableSelHandler_Impl() +{ +} + +uno::Sequence < sal_Int32 > SwAccAllTableSelHandler_Impl::GetSelSequence() +{ + OSL_ENSURE( m_nCount >= 0, "underflow" ); + uno::Sequence < sal_Int32 > aRet( m_nCount ); + sal_Int32 *pRet = aRet.getArray(); + sal_Int32 nPos = 0; + size_t nSize = m_aSelected.size(); + for( size_t i=0; i < nSize && nPos < m_nCount; i++ ) + { + if( m_aSelected[i] ) + { + *pRet++ = i; + nPos++; + } + } + + OSL_ENSURE( nPos == m_nCount, "count is wrong" ); + + return aRet; +} + +void SwAccAllTableSelHandler_Impl::Unselect( sal_Int32 nRowOrCol, + sal_Int32 nExt ) +{ + OSL_ENSURE( o3tl::make_unsigned( nRowOrCol ) < m_aSelected.size(), + "index too large" ); + OSL_ENSURE( o3tl::make_unsigned( nRowOrCol+nExt ) <= m_aSelected.size(), + "extent too large" ); + while( nExt ) + { + if( m_aSelected[static_cast< size_t >( nRowOrCol )] ) + { + m_aSelected[static_cast< size_t >( nRowOrCol )] = false; + m_nCount--; + } + nExt--; + nRowOrCol++; + } +} + +const SwSelBoxes *SwAccessibleTable::GetSelBoxes() const +{ + const SwSelBoxes *pSelBoxes = nullptr; + const SwCursorShell *pCSh = GetCursorShell(); + if( (pCSh != nullptr) && pCSh->IsTableMode() ) + { + pSelBoxes = &pCSh->GetTableCursor()->GetSelectedBoxes(); + } + + return pSelBoxes; +} + +void SwAccessibleTable::FireTableChangeEvent( + const SwAccessibleTableData_Impl& rTableData ) +{ + AccessibleTableModelChange aModelChange; + aModelChange.Type = AccessibleTableModelChangeType::UPDATE; + aModelChange.FirstRow = 0; + aModelChange.LastRow = rTableData.GetRowCount() - 1; + aModelChange.FirstColumn = 0; + aModelChange.LastColumn = rTableData.GetColumnCount() - 1; + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TABLE_MODEL_CHANGED; + aEvent.NewValue <<= aModelChange; + + FireAccessibleEvent( aEvent ); +} + +const SwTableBox* SwAccessibleTable::GetTableBox( sal_Int32 nChildIndex ) const +{ + OSL_ENSURE( nChildIndex >= 0, "Illegal child index." ); + OSL_ENSURE( nChildIndex < const_cast<SwAccessibleTable*>(this)->getAccessibleChildCount(), "Illegal child index." ); // #i77106# + + const SwTableBox* pBox = nullptr; + + // get table box for 'our' table cell + SwAccessibleChild aCell( GetChild( *const_cast<SwAccessibleMap*>(GetMap()), nChildIndex ) ); + if( aCell.GetSwFrame() ) + { + const SwFrame* pChildFrame = aCell.GetSwFrame(); + if( (pChildFrame != nullptr) && pChildFrame->IsCellFrame() ) + { + const SwCellFrame* pCellFrame = + static_cast<const SwCellFrame*>( pChildFrame ); + pBox = pCellFrame->GetTabBox(); + } + } + + OSL_ENSURE( pBox != nullptr, "We need the table box." ); + return pBox; +} + +bool SwAccessibleTable::IsChildSelected( sal_Int32 nChildIndex ) const +{ + bool bRet = false; + const SwSelBoxes* pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + const SwTableBox* pBox = GetTableBox( nChildIndex ); + OSL_ENSURE( pBox != nullptr, "We need the table box." ); + bRet = pSelBoxes->find( const_cast<SwTableBox*>( pBox ) ) != pSelBoxes->end(); + } + + return bRet; +} + +sal_Int32 SwAccessibleTable::GetIndexOfSelectedChild( + sal_Int32 nSelectedChildIndex ) const +{ + // iterate over all children to n-th isAccessibleChildSelected() + sal_Int32 nChildren = const_cast<SwAccessibleTable*>(this)->getAccessibleChildCount(); // #i77106# + if( nSelectedChildIndex >= nChildren ) + return -1; + + sal_Int32 n = 0; + while( n < nChildren ) + { + if( IsChildSelected( n ) ) + { + if( 0 == nSelectedChildIndex ) + break; + else + --nSelectedChildIndex; + } + ++n; + } + + return n < nChildren ? n : -1; +} + +void SwAccessibleTable::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + //Add resizable state to table + rStateSet.AddState( AccessibleStateType::RESIZABLE ); + // MULTISELECTABLE + rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE ); + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell ) + rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE ); +} + +SwAccessibleTable::SwAccessibleTable( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwTabFrame* pTabFrame ) : + SwAccessibleContext( pInitMap, AccessibleRole::TABLE, pTabFrame ) +{ + const SwFrameFormat* pFrameFormat = pTabFrame->GetFormat(); + if(pFrameFormat) + StartListening(const_cast<SwFrameFormat*>(pFrameFormat)->GetNotifier()); + + SetName( pFrameFormat->GetName() + "-" + OUString::number( pTabFrame->GetPhyPageNum() ) ); + + const OUString sArg1( static_cast< const SwTabFrame * >( GetFrame() )->GetFormat()->GetName() ); + const OUString sArg2( GetFormattedPageNumber() ); + + m_sDesc = GetResource( STR_ACCESS_TABLE_DESC, &sArg1, &sArg2 ); + UpdateTableData(); +} + +SwAccessibleTable::~SwAccessibleTable() +{ + SolarMutexGuard aGuard; + + mpTableData.reset(); +} + +void SwAccessibleTable::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + EndListeningAll(); + } + else if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint); + const sal_uInt16 nWhich = pLegacyHint->GetWhich(); + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(GetFrame()); + if(nWhich == RES_NAME_CHANGED && pTabFrame) + { + const SwFrameFormat *pFrameFormat = pTabFrame->GetFormat(); + + const OUString sOldName( GetName() ); + const OUString sNewTabName = pFrameFormat->GetName(); + + SetName( sNewTabName + "-" + OUString::number( pTabFrame->GetPhyPageNum() ) ); + + if( sOldName != GetName() ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + aEvent.OldValue <<= sOldName; + aEvent.NewValue <<= GetName(); + FireAccessibleEvent( aEvent ); + } + + const OUString sOldDesc( m_sDesc ); + const OUString sArg2( GetFormattedPageNumber() ); + + m_sDesc = GetResource( STR_ACCESS_TABLE_DESC, &sNewTabName, &sArg2 ); + if( m_sDesc != sOldDesc ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED; + aEvent.OldValue <<= sOldDesc; + aEvent.NewValue <<= m_sDesc; + FireAccessibleEvent( aEvent ); + } + } + } +} + +uno::Any SwAccessibleTable::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet; + if ( rType == cppu::UnoType<XAccessibleTable>::get() ) + { + uno::Reference<XAccessibleTable> xThis( this ); + aRet <<= xThis; + } + else if ( rType == cppu::UnoType<XAccessibleSelection>::get() ) + { + uno::Reference<XAccessibleSelection> xSelection( this ); + aRet <<= xSelection; + } + else if ( rType == cppu::UnoType<XAccessibleTableSelection>::get() ) + { + uno::Reference<XAccessibleTableSelection> xTableExtent( this ); + aRet <<= xTableExtent; + } + else + { + aRet = SwAccessibleContext::queryInterface(rType); + } + + return aRet; +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SwAccessibleTable::getTypes() +{ + return cppu::OTypeCollection( + cppu::UnoType<XAccessibleSelection>::get(), + cppu::UnoType<XAccessibleTable>::get(), + SwAccessibleContext::getTypes() ).getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleTable::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// #i77106# +std::unique_ptr<SwAccessibleTableData_Impl> SwAccessibleTable::CreateNewTableData() +{ + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>( GetFrame() ); + return std::unique_ptr<SwAccessibleTableData_Impl>(new SwAccessibleTableData_Impl( *GetMap(), pTabFrame, IsInPagePreview() )); +} + +void SwAccessibleTable::UpdateTableData() +{ + // #i77106# - usage of new method <CreateNewTableData()> + mpTableData = CreateNewTableData(); +} + +void SwAccessibleTable::ClearTableData() +{ + mpTableData.reset(); +} + +OUString SAL_CALL SwAccessibleTable::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return m_sDesc; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleRowCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return GetTableData().GetRowCount(); +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleColumnCount( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return GetTableData().GetColumnCount(); +} + +OUString SAL_CALL SwAccessibleTable::getAccessibleRowDescription( + sal_Int32 nRow ) +{ + // #i87532# - determine table cell in <nRow>th row and + // in first column of row header table and return its text content. + OUString sRowDesc; + + GetTableData().CheckRowAndCol(nRow, 0, this); + + uno::Reference< XAccessibleTable > xTableRowHeader = getAccessibleRowHeaders(); + if ( xTableRowHeader.is() ) + { + uno::Reference< XAccessible > xRowHeaderCell = + xTableRowHeader->getAccessibleCellAt( nRow, 0 ); + OSL_ENSURE( xRowHeaderCell.is(), + "<SwAccessibleTable::getAccessibleRowDescription(..)> - missing row header cell -> serious issue." ); + uno::Reference< XAccessibleContext > xRowHeaderCellContext = + xRowHeaderCell->getAccessibleContext(); + const sal_Int32 nCellChildCount( xRowHeaderCellContext->getAccessibleChildCount() ); + for ( sal_Int32 nChildIndex = 0; nChildIndex < nCellChildCount; ++nChildIndex ) + { + uno::Reference< XAccessible > xChild = xRowHeaderCellContext->getAccessibleChild( nChildIndex ); + uno::Reference< XAccessibleText > xChildText( xChild, uno::UNO_QUERY ); + if ( xChildText.is() ) + { + sRowDesc += xChildText->getText(); + } + } + } + + return sRowDesc; +} + +OUString SAL_CALL SwAccessibleTable::getAccessibleColumnDescription( + sal_Int32 nColumn ) +{ + // #i87532# - determine table cell in first row and + // in <nColumn>th column of column header table and return its text content. + OUString sColumnDesc; + + GetTableData().CheckRowAndCol(0, nColumn, this); + + uno::Reference< XAccessibleTable > xTableColumnHeader = getAccessibleColumnHeaders(); + if ( xTableColumnHeader.is() ) + { + uno::Reference< XAccessible > xColumnHeaderCell = + xTableColumnHeader->getAccessibleCellAt( 0, nColumn ); + OSL_ENSURE( xColumnHeaderCell.is(), + "<SwAccessibleTable::getAccessibleColumnDescription(..)> - missing column header cell -> serious issue." ); + uno::Reference< XAccessibleContext > xColumnHeaderCellContext = + xColumnHeaderCell->getAccessibleContext(); + const sal_Int32 nCellChildCount( xColumnHeaderCellContext->getAccessibleChildCount() ); + for ( sal_Int32 nChildIndex = 0; nChildIndex < nCellChildCount; ++nChildIndex ) + { + uno::Reference< XAccessible > xChild = xColumnHeaderCellContext->getAccessibleChild( nChildIndex ); + uno::Reference< XAccessibleText > xChildText( xChild, uno::UNO_QUERY ); + if ( xChildText.is() ) + { + sColumnDesc += xChildText->getText(); + } + } + } + + return sColumnDesc; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleRowExtentAt( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + sal_Int32 nExtend = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + UpdateTableData(); + GetTableData().CheckRowAndCol( nRow, nColumn, this ); + + Int32Set_Impl::const_iterator aSttCol( + GetTableData().GetColumnIter( nColumn ) ); + Int32Set_Impl::const_iterator aSttRow( + GetTableData().GetRowIter( nRow ) ); + const SwFrame *pCellFrame = GetTableData().GetCellAtPos( *aSttCol, *aSttRow ); + if( pCellFrame ) + { + sal_Int32 nBottom = pCellFrame->getFrameArea().Bottom(); + nBottom -= GetFrame()->getFrameArea().Top(); + Int32Set_Impl::const_iterator aEndRow( + GetTableData().GetRows().upper_bound( nBottom ) ); + nExtend = + static_cast< sal_Int32 >( std::distance( aSttRow, aEndRow ) ); + } + + return nExtend; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleColumnExtentAt( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + sal_Int32 nExtend = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + UpdateTableData(); + + GetTableData().CheckRowAndCol( nRow, nColumn, this ); + + Int32Set_Impl::const_iterator aSttCol( + GetTableData().GetColumnIter( nColumn ) ); + Int32Set_Impl::const_iterator aSttRow( + GetTableData().GetRowIter( nRow ) ); + const SwFrame *pCellFrame = GetTableData().GetCellAtPos( *aSttCol, *aSttRow ); + if( pCellFrame ) + { + sal_Int32 nRight = pCellFrame->getFrameArea().Right(); + nRight -= GetFrame()->getFrameArea().Left(); + Int32Set_Impl::const_iterator aEndCol( + GetTableData().GetColumns().upper_bound( nRight ) ); + nExtend = + static_cast< sal_Int32 >( std::distance( aSttCol, aEndCol ) ); + } + + return nExtend; +} + +uno::Reference< XAccessibleTable > SAL_CALL + SwAccessibleTable::getAccessibleRowHeaders( ) +{ + // Row headers aren't supported + return uno::Reference< XAccessibleTable >(); +} + +uno::Reference< XAccessibleTable > SAL_CALL + SwAccessibleTable::getAccessibleColumnHeaders( ) +{ + SolarMutexGuard aGuard; + + // #i87532# - assure that return accessible object is empty, + // if no column header exists. + rtl::Reference<SwAccessibleTableColHeaders> pTableColHeaders = + new SwAccessibleTableColHeaders(GetMap()->shared_from_this(), + static_cast<const SwTabFrame *>(GetFrame())); + if ( pTableColHeaders->getAccessibleChildCount() <= 0 ) + { + return uno::Reference< XAccessibleTable >(); + } + + return pTableColHeaders; +} + +uno::Sequence< sal_Int32 > SAL_CALL SwAccessibleTable::getSelectedAccessibleRows() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + sal_Int32 nRows = GetTableData().GetRowCount(); + SwAccAllTableSelHandler_Impl aSelRows( nRows ); + + GetTableData().GetSelection( 0, nRows, *pSelBoxes, aSelRows, + false ); + + return aSelRows.GetSelSequence(); + } + else + { + return uno::Sequence< sal_Int32 >( 0 ); + } +} + +uno::Sequence< sal_Int32 > SAL_CALL SwAccessibleTable::getSelectedAccessibleColumns() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + sal_Int32 nCols = GetTableData().GetColumnCount(); + SwAccAllTableSelHandler_Impl aSelCols( nCols ); + + GetTableData().GetSelection( 0, nCols, *pSelBoxes, aSelCols, true ); + + return aSelCols.GetSelSequence(); + } + else + { + return uno::Sequence< sal_Int32 >( 0 ); + } +} + +sal_Bool SAL_CALL SwAccessibleTable::isAccessibleRowSelected( sal_Int32 nRow ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + GetTableData().CheckRowAndCol( nRow, 0, this ); + + bool bRet; + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + SwAccSingleTableSelHandler_Impl aSelRow; + GetTableData().GetSelection( nRow, nRow+1, *pSelBoxes, aSelRow, + false ); + bRet = aSelRow.IsSelected(); + } + else + { + bRet = false; + } + + return bRet; +} + +sal_Bool SAL_CALL SwAccessibleTable::isAccessibleColumnSelected( + sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + GetTableData().CheckRowAndCol( 0, nColumn, this ); + + bool bRet; + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + SwAccSingleTableSelHandler_Impl aSelCol; + + GetTableData().GetSelection( nColumn, nColumn+1, *pSelBoxes, aSelCol, + true ); + bRet = aSelCol.IsSelected(); + } + else + { + bRet = false; + } + + return bRet; +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleTable::getAccessibleCellAt( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + uno::Reference< XAccessible > xRet; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwFrame *pCellFrame = + GetTableData().GetCell( nRow, nColumn, this ); + if( pCellFrame ) + xRet = GetMap()->GetContext( pCellFrame ); + + return xRet; +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleTable::getAccessibleCaption() +{ + // captions aren't supported + return uno::Reference< XAccessible >(); +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleTable::getAccessibleSummary() +{ + // summaries aren't supported + return uno::Reference< XAccessible >(); +} + +sal_Bool SAL_CALL SwAccessibleTable::isAccessibleSelected( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + bool bRet = false; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwFrame *pFrame = + GetTableData().GetCell( nRow, nColumn, this ); + if( pFrame && pFrame->IsCellFrame() ) + { + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + const SwCellFrame *pCFrame = static_cast < const SwCellFrame * >( pFrame ); + SwTableBox *pBox = + const_cast< SwTableBox *>( pCFrame->GetTabBox() ); + bRet = pSelBoxes->find( pBox ) != pSelBoxes->end(); + } + } + + return bRet; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleIndex( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + sal_Int32 nRet = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + SwAccessibleChild aCell( GetTableData().GetCell( nRow, nColumn, this )); + if ( aCell.IsValid() ) + { + nRet = GetChildIndex( *(GetMap()), aCell ); + } + + return nRet; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleRow( sal_Int32 nChildIndex ) +{ + sal_Int32 nRet = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // #i77106# + if ( ( nChildIndex < 0 ) || + ( nChildIndex >= getAccessibleChildCount() ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + SwAccessibleChild aCell( GetChild( *(GetMap()), nChildIndex ) ); + if ( aCell.GetSwFrame() ) + { + sal_Int32 nTop = aCell.GetSwFrame()->getFrameArea().Top(); + nTop -= GetFrame()->getFrameArea().Top(); + Int32Set_Impl::const_iterator aRow( + GetTableData().GetRows().lower_bound( nTop ) ); + nRet = static_cast< sal_Int32 >( std::distance( + GetTableData().GetRows().begin(), aRow ) ); + } + else + { + OSL_ENSURE( !aCell.IsValid(), "SwAccessibleTable::getAccessibleColumn:" + "aCell not expected to be valid."); + + throw lang::IndexOutOfBoundsException(); + } + + return nRet; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleColumn( + sal_Int32 nChildIndex ) +{ + sal_Int32 nRet = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // #i77106# + if ( ( nChildIndex < 0 ) || + ( nChildIndex >= getAccessibleChildCount() ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + SwAccessibleChild aCell( GetChild( *(GetMap()), nChildIndex ) ); + if ( aCell.GetSwFrame() ) + { + sal_Int32 nLeft = aCell.GetSwFrame()->getFrameArea().Left(); + nLeft -= GetFrame()->getFrameArea().Left(); + Int32Set_Impl::const_iterator aCol( + GetTableData().GetColumns().lower_bound( nLeft ) ); + nRet = static_cast< sal_Int32 >( std::distance( + GetTableData().GetColumns().begin(), aCol ) ); + } + else + { + OSL_ENSURE( !aCell.IsValid(), "SwAccessibleTable::getAccessibleColumn:" + "aCell not expected to be valid."); + + throw lang::IndexOutOfBoundsException(); + } + + return nRet; +} + +OUString SAL_CALL SwAccessibleTable::getImplementationName() +{ + return "com.sun.star.comp.Writer.SwAccessibleTableView"; +} + +sal_Bool SAL_CALL SwAccessibleTable::supportsService( + const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleTable::getSupportedServiceNames() +{ + return { "com.sun.star.table.AccessibleTableView", sAccessibleServiceName }; +} + +void SwAccessibleTable::InvalidatePosOrSize( const SwRect& rOldBox ) +{ + SolarMutexGuard aGuard; + + //need to update children + std::unique_ptr<SwAccessibleTableData_Impl> pNewTableData = CreateNewTableData(); + if( !pNewTableData->CompareExtents( GetTableData() ) ) + { + mpTableData = std::move(pNewTableData); + FireTableChangeEvent(*mpTableData); + } + if( HasTableData() ) + GetTableData().SetTablePos( GetFrame()->getFrameArea().Pos() ); + + SwAccessibleContext::InvalidatePosOrSize( rOldBox ); +} + +void SwAccessibleTable::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + SolarMutexGuard aGuard; + EndListeningAll(); + SwAccessibleContext::Dispose(bRecursive, bCanSkipInvisible); +} + +void SwAccessibleTable::DisposeChild( const SwAccessibleChild& rChildFrameOrObj, + bool bRecursive, bool bCanSkipInvisible ) +{ + SolarMutexGuard aGuard; + + const SwFrame *pFrame = rChildFrameOrObj.GetSwFrame(); + OSL_ENSURE( pFrame, "frame expected" ); + if( HasTableData() ) + { + FireTableChangeEvent( GetTableData() ); + ClearTableData(); + } + + // There are two reason why this method has been called. The first one + // is there is no context for pFrame. The method is then called by + // the map, and we have to call our superclass. + // The other situation is that we have been call by a call to get notified + // about its change. We then must not call the superclass + uno::Reference< XAccessible > xAcc( GetMap()->GetContext( pFrame, false ) ); + if( !xAcc.is() ) + SwAccessibleContext::DisposeChild( rChildFrameOrObj, bRecursive, bCanSkipInvisible ); +} + +void SwAccessibleTable::InvalidateChildPosOrSize( const SwAccessibleChild& rChildFrameOrObj, + const SwRect& rOldBox ) +{ + SolarMutexGuard aGuard; + + if( HasTableData() ) + { + SAL_WARN_IF( HasTableData() && + GetFrame()->getFrameArea().Pos() != GetTableData().GetTablePos(), + "sw.a11y", "table has invalid position" ); + if( HasTableData() ) + { + std::unique_ptr<SwAccessibleTableData_Impl> pNewTableData = CreateNewTableData(); // #i77106# + if( !pNewTableData->CompareExtents( GetTableData() ) ) + { + if (pNewTableData->GetRowCount() != mpTableData->GetRowCount() + && 1 < GetTableData().GetRowCount()) + { + Int32Set_Impl::const_iterator aSttCol( GetTableData().GetColumnIter( 0 ) ); + Int32Set_Impl::const_iterator aSttRow( GetTableData().GetRowIter( 1 ) ); + const SwFrame *pCellFrame = GetTableData().GetCellAtPos( *aSttCol, *aSttRow ); + Int32Set_Impl::const_iterator aSttCol2( pNewTableData->GetColumnIter( 0 ) ); + Int32Set_Impl::const_iterator aSttRow2( pNewTableData->GetRowIter( 0 ) ); + const SwFrame *pCellFrame2 = pNewTableData->GetCellAtPos( *aSttCol2, *aSttRow2 ); + + if(pCellFrame == pCellFrame2) + { + AccessibleTableModelChange aModelChange; + aModelChange.Type = AccessibleTableModelChangeType::UPDATE; + aModelChange.FirstRow = 0; + aModelChange.LastRow = mpTableData->GetRowCount() - 1; + aModelChange.FirstColumn = 0; + aModelChange.LastColumn = mpTableData->GetColumnCount() - 1; + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED; + aEvent.NewValue <<= aModelChange; + + FireAccessibleEvent( aEvent ); + } + } + else + FireTableChangeEvent( GetTableData() ); + ClearTableData(); + mpTableData = std::move(pNewTableData); + } + } + } + + // #i013961# - always call super class method + SwAccessibleContext::InvalidateChildPosOrSize( rChildFrameOrObj, rOldBox ); +} + +// XAccessibleSelection + +void SAL_CALL SwAccessibleTable::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( (nChildIndex < 0) || (nChildIndex >= getAccessibleChildCount()) ) // #i77106# + throw lang::IndexOutOfBoundsException(); + + // preliminaries: get 'our' table box, and get the cursor shell + const SwTableBox* pBox = GetTableBox( nChildIndex ); + OSL_ENSURE( pBox != nullptr, "We need the table box." ); + + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell == nullptr ) + return; + + // assure, that child, identified by the given index, isn't already selected. + if ( IsChildSelected( nChildIndex ) ) + { + return; + } + + // now we can start to do the work: check whether we already have + // a table selection (in 'our' table). If so, extend the + // selection, else select the current cell. + + // if we have a selection in a table, check if it's in the + // same table that we're trying to select in + const SwTableNode* pSelectedTable = pCursorShell->IsCursorInTable(); + if( pSelectedTable != nullptr ) + { + // get top-most table line + const SwTableLine* pUpper = pBox->GetUpper(); + while( pUpper->GetUpper() != nullptr ) + pUpper = pUpper->GetUpper()->GetUpper(); + sal_uInt16 nPos = + pSelectedTable->GetTable().GetTabLines().GetPos( pUpper ); + if( nPos == USHRT_MAX ) + pSelectedTable = nullptr; + } + + // create the new selection + const SwStartNode* pStartNode = pBox->GetSttNd(); + if( pSelectedTable == nullptr || !pCursorShell->GetTableCrs() ) + { + pCursorShell->StartAction(); + // Set cursor into current cell. This deletes any table cursor. + SwPaM aPaM( *pStartNode ); + aPaM.Move( fnMoveForward, GoInNode ); + Select( aPaM ); + // Move cursor to the end of the table creating a selection and a table + // cursor. + pCursorShell->SetMark(); + pCursorShell->MoveTable( GotoCurrTable, fnTableEnd ); + // now set the cursor into the cell again. + SwPaM *pPaM = pCursorShell->GetTableCrs() ? pCursorShell->GetTableCrs() + : pCursorShell->GetCursor(); + *pPaM->GetPoint() = *pPaM->GetMark(); + pCursorShell->EndAction(); + // we now have one cell selected! + } + else + { + // if the cursor is already in this table, + // expand the current selection (i.e., set + // point to new position; keep mark) + SwPaM aPaM( *pStartNode ); + aPaM.Move( fnMoveForward, GoInNode ); + aPaM.SetMark(); + const SwPaM *pPaM = pCursorShell->GetTableCrs() ? pCursorShell->GetTableCrs() + : pCursorShell->GetCursor(); + *(aPaM.GetMark()) = *pPaM->GetMark(); + Select( aPaM ); + + } +} + +sal_Bool SAL_CALL SwAccessibleTable::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( (nChildIndex < 0) || (nChildIndex >= getAccessibleChildCount()) ) // #i77106# + throw lang::IndexOutOfBoundsException(); + + return IsChildSelected( nChildIndex ); +} + +void SAL_CALL SwAccessibleTable::clearAccessibleSelection( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + pCursorShell->StartAction(); + pCursorShell->ClearMark(); + pCursorShell->EndAction(); + } +} + +void SAL_CALL SwAccessibleTable::selectAllAccessibleChildren( ) +{ + // first clear selection, then select first and last child + clearAccessibleSelection(); + selectAccessibleChild( 0 ); + selectAccessibleChild( getAccessibleChildCount()-1 ); // #i77106# +} + +sal_Int32 SAL_CALL SwAccessibleTable::getSelectedAccessibleChildCount( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // iterate over all children and count isAccessibleChildSelected() + sal_Int32 nCount = 0; + + sal_Int32 nChildren = getAccessibleChildCount(); // #i71106# + for( sal_Int32 n = 0; n < nChildren; n++ ) + if( IsChildSelected( n ) ) + nCount++; + + return nCount; +} + +uno::Reference<XAccessible> SAL_CALL SwAccessibleTable::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking (part 1): index lower 0 + if( nSelectedChildIndex < 0 ) + throw lang::IndexOutOfBoundsException(); + + sal_Int32 nChildIndex = GetIndexOfSelectedChild( nSelectedChildIndex ); + + // parameter checking (part 2): index higher than selected children? + if( nChildIndex < 0 ) + throw lang::IndexOutOfBoundsException(); + + // #i77106# + if ( nChildIndex >= getAccessibleChildCount() ) + { + throw lang::IndexOutOfBoundsException(); + } + + return getAccessibleChild( nChildIndex ); +} + +// index has to be treated as global child index. +void SAL_CALL SwAccessibleTable::deselectAccessibleChild( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + SwCursorShell* pCursorShell = GetCursorShell(); + + // index has to be treated as global child index + if ( !pCursorShell ) + throw lang::IndexOutOfBoundsException(); + + // assure, that given child index is in bounds. + if ( nChildIndex < 0 || nChildIndex >= getAccessibleChildCount() ) // #i77106# + throw lang::IndexOutOfBoundsException(); + + // assure, that child, identified by the given index, is selected. + if ( !IsChildSelected( nChildIndex ) ) + return; + + const SwTableBox* pBox = GetTableBox( nChildIndex ); + OSL_ENSURE( pBox != nullptr, "We need the table box." ); + + // If we unselect point, then set cursor to mark. If we clear another + // selected box, then set cursor to point. + // reduce selection to mark. + SwPaM *pPaM = pCursorShell->GetTableCrs() ? pCursorShell->GetTableCrs() + : pCursorShell->GetCursor(); + bool bDeselectPoint = + pBox->GetSttNd() == + pPaM->GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + + SwPaM aPaM( bDeselectPoint ? *pPaM->GetMark() : *pPaM->GetPoint() ); + + pCursorShell->StartAction(); + + // Set cursor into either point or mark + Select( aPaM ); + // Move cursor to the end of the table creating a selection and a table + // cursor. + pCursorShell->SetMark(); + pCursorShell->MoveTable( GotoCurrTable, fnTableEnd ); + // now set the cursor into the cell again. + pPaM = pCursorShell->GetTableCrs() ? pCursorShell->GetTableCrs() + : pCursorShell->GetCursor(); + *pPaM->GetPoint() = *pPaM->GetMark(); + pCursorShell->EndAction(); +} + +sal_Int32 SAL_CALL SwAccessibleTable::getBackground() +{ + const SvxBrushItem &rBack = GetFrame()->GetAttrSet()->GetBackground(); + Color crBack = rBack.GetColor(); + + if (COL_AUTO == crBack) + { + uno::Reference<XAccessible> xAccDoc = getAccessibleParent(); + if (xAccDoc.is()) + { + uno::Reference<XAccessibleComponent> xComponentDoc(xAccDoc,uno::UNO_QUERY); + if (xComponentDoc.is()) + { + crBack = Color(ColorTransparency, xComponentDoc->getBackground()); + } + } + } + return sal_Int32(crBack); +} + +void SwAccessibleTable::FireSelectionEvent( ) +{ + AccessibleEventObject aEvent; + + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE; + + for (const auto& rCell : m_vecCellRemove) + { + // fdo#57197: check if the object is still alive + uno::Reference<XAccessible> const xAcc(rCell.second); + if (xAcc.is()) + { + SwAccessibleContext *const pAccCell(rCell.first); + assert(pAccCell); + pAccCell->FireAccessibleEvent(aEvent); + } + } + + if (m_vecCellAdd.size() <= SELECTION_WITH_NUM) + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_ADD; + for (const auto& rCell : m_vecCellAdd) + { + // fdo#57197: check if the object is still alive + uno::Reference<XAccessible> const xAcc(rCell.second); + if (xAcc.is()) + { + SwAccessibleContext *const pAccCell(rCell.first); + assert(pAccCell); + pAccCell->FireAccessibleEvent(aEvent); + } + } + return ; + } + else + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN; + FireAccessibleEvent(aEvent); + } +} + +void SwAccessibleTable::AddSelectionCell( + SwAccessibleContext *const pAccCell, bool const bAddOrRemove) +{ + uno::Reference<XAccessible> const xTmp(pAccCell); + if (bAddOrRemove) + { + m_vecCellAdd.emplace_back(pAccCell, xTmp); + } + else + { + m_vecCellRemove.emplace_back(pAccCell, xTmp); + } +} + +// XAccessibleTableSelection +sal_Bool SAL_CALL SwAccessibleTable::selectRow( sal_Int32 row ) +{ + SolarMutexGuard g; + + if( isAccessibleColumnSelected( row ) ) + return true; + + tools::Long lColumnCount = getAccessibleColumnCount(); + for(tools::Long lCol = 0; lCol < lColumnCount; lCol ++) + { + tools::Long lChildIndex = getAccessibleIndex(row, lCol); + selectAccessibleChild(lChildIndex); + } + + return true; +} +sal_Bool SAL_CALL SwAccessibleTable::selectColumn( sal_Int32 column ) +{ + SolarMutexGuard g; + + if( isAccessibleColumnSelected( column ) ) + return true; + + sal_Int32 lRowCount = getAccessibleRowCount(); + + for(sal_Int32 lRow = 0; lRow < lRowCount; lRow ++) + { + sal_Int32 lChildIndex = getAccessibleIndex(lRow, column); + selectAccessibleChild(lChildIndex); + } + return true; +} + +sal_Bool SAL_CALL SwAccessibleTable::unselectRow( sal_Int32 row ) +{ + SolarMutexGuard g; + + if( isAccessibleSelected( row , 0 ) && isAccessibleSelected( row , getAccessibleColumnCount()-1 ) ) + { + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + pCursorShell->StartAction(); + pCursorShell->ClearMark(); + pCursorShell->EndAction(); + return true; + } + } + return true; +} + +sal_Bool SAL_CALL SwAccessibleTable::unselectColumn( sal_Int32 column ) +{ + SolarMutexGuard g; + + if( isAccessibleSelected( 0 , column ) && isAccessibleSelected( getAccessibleRowCount()-1,column)) + { + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + pCursorShell->StartAction(); + pCursorShell->ClearMark(); + pCursorShell->EndAction(); + return true; + } + } + return true; +} + +// #i77106# - implementation of class <SwAccessibleTableColHeaders> +SwAccessibleTableColHeaders::SwAccessibleTableColHeaders( + std::shared_ptr<SwAccessibleMap> const& pMap, + const SwTabFrame *const pTabFrame) + : SwAccessibleTable(pMap, pTabFrame) +{ + SolarMutexGuard aGuard; + + const SwFrameFormat* pFrameFormat = pTabFrame->GetFormat(); + if(pFrameFormat) + StartListening(const_cast<SwFrameFormat*>(pFrameFormat)->GetNotifier()); + const OUString aName = pFrameFormat->GetName() + "-ColumnHeaders"; + + SetName( aName + "-" + OUString::number( pTabFrame->GetPhyPageNum() ) ); + + const OUString sArg2( GetFormattedPageNumber() ); + + SetDesc( GetResource( STR_ACCESS_TABLE_DESC, &aName, &sArg2 ) ); + + NotRegisteredAtAccessibleMap(); // #i85634# +} + +std::unique_ptr<SwAccessibleTableData_Impl> SwAccessibleTableColHeaders::CreateNewTableData() +{ + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>( GetFrame() ); + return std::unique_ptr<SwAccessibleTableData_Impl>(new SwAccessibleTableData_Impl( *(GetMap()), pTabFrame, IsInPagePreview(), true )); +} + +void SwAccessibleTableColHeaders::Notify(const SfxHint& ) +{ +} + +// XInterface +uno::Any SAL_CALL SwAccessibleTableColHeaders::queryInterface( const uno::Type& aType ) +{ + return SwAccessibleTable::queryInterface( aType ); +} + +// XAccessibleContext +sal_Int32 SAL_CALL SwAccessibleTableColHeaders::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nCount = 0; + + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>( GetFrame() ); + const SwAccessibleChildSList aVisList( GetVisArea(), *pTabFrame, *(GetMap()) ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( IsInPagePreview() ) ) + { + nCount++; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + if ( !rLower.GetSwFrame()->IsRowFrame() || + pTabFrame->IsInHeadline( *(rLower.GetSwFrame()) ) ) + { + nCount += SwAccessibleFrame::GetChildCount( *(GetMap()), + GetVisArea(), + rLower.GetSwFrame(), + IsInPagePreview() ); + } + } + ++aIter; + } + + return nCount; +} + +uno::Reference< XAccessible> SAL_CALL + SwAccessibleTableColHeaders::getAccessibleChild (sal_Int32 nIndex) +{ + if ( nIndex < 0 || nIndex >= getAccessibleChildCount() ) + { + throw lang::IndexOutOfBoundsException(); + } + + return SwAccessibleTable::getAccessibleChild( nIndex ); +} + +// XAccessibleTable +uno::Reference< XAccessibleTable > + SAL_CALL SwAccessibleTableColHeaders::getAccessibleRowHeaders() +{ + return uno::Reference< XAccessibleTable >(); +} + +uno::Reference< XAccessibleTable > + SAL_CALL SwAccessibleTableColHeaders::getAccessibleColumnHeaders() +{ + return uno::Reference< XAccessibleTable >(); +} + +// XServiceInfo + +OUString SAL_CALL SwAccessibleTableColHeaders::getImplementationName() +{ + static constexpr OUStringLiteral sImplName + = u"com.sun.star.comp.Writer.SwAccessibleTableColumnHeadersView"; + return sImplName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acctable.hxx b/sw/source/core/access/acctable.hxx new file mode 100644 index 000000000..c31ecca95 --- /dev/null +++ b/sw/source/core/access/acctable.hxx @@ -0,0 +1,277 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/accessibility/XAccessibleTable.hpp> +#include <com/sun/star/accessibility/XAccessibleTableSelection.hpp> +#include <vector> +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> + +#include <svl/listener.hxx> + +#include "acccontext.hxx" + +class SwTabFrame; +class SwAccessibleTableData_Impl; +class SwTableBox; +class SwSelBoxes; + +namespace sw::access { + class SwAccessibleChild; +} + +class SwAccessibleTable : + public SwAccessibleContext, + public css::accessibility::XAccessibleTable, + public css::accessibility::XAccessibleSelection, + public css::accessibility::XAccessibleTableSelection, + public SvtListener +{ + std::unique_ptr<SwAccessibleTableData_Impl> mpTableData; // the table's data, protected by SolarMutex + OUString m_sDesc; + const SwSelBoxes *GetSelBoxes() const; + + void FireTableChangeEvent( const SwAccessibleTableData_Impl& rTableData ); + + /** get the SwTableBox* for the given child */ + const SwTableBox* GetTableBox( sal_Int32 ) const; + + bool IsChildSelected( sal_Int32 nChildIndex ) const; + + sal_Int32 GetIndexOfSelectedChild( sal_Int32 nSelectedChildIndex ) const; + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets MULTISELECTABLE(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual ~SwAccessibleTable() override; + + // #i77106# + void SetDesc( const OUString& sNewDesc ) + { + m_sDesc = sNewDesc; + } + + virtual std::unique_ptr<SwAccessibleTableData_Impl> CreateNewTableData(); // #i77106# + + // force update of table data + void UpdateTableData(); + + // remove the current table data + void ClearTableData(); + + // get table data, update if necessary + inline SwAccessibleTableData_Impl& GetTableData(); + + // Is table data evailable? + bool HasTableData() const { return (mpTableData != nullptr); } + + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleTable(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwTabFrame* pTableFrame); + + // XInterface + + // (XInterface methods need to be implemented to disambiguate + // between those inherited through SwAccessibleContext and + // XAccessibleTable). + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) noexcept override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) noexcept override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleContext + + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XAccessibleTable + + virtual sal_Int32 SAL_CALL getAccessibleRowCount() override; + virtual sal_Int32 SAL_CALL getAccessibleColumnCount( ) override; + virtual OUString SAL_CALL getAccessibleRowDescription( + sal_Int32 nRow ) override; + virtual OUString SAL_CALL getAccessibleColumnDescription( + sal_Int32 nColumn ) override; + virtual sal_Int32 SAL_CALL getAccessibleRowExtentAt( + sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual sal_Int32 SAL_CALL getAccessibleColumnExtentAt( + sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual css::uno::Reference< + css::accessibility::XAccessibleTable > + SAL_CALL getAccessibleRowHeaders( ) override; + virtual css::uno::Reference< + css::accessibility::XAccessibleTable > + SAL_CALL getAccessibleColumnHeaders( ) override; + virtual css::uno::Sequence< sal_Int32 > SAL_CALL + getSelectedAccessibleRows( ) override; + virtual css::uno::Sequence< sal_Int32 > SAL_CALL + getSelectedAccessibleColumns( ) override; + virtual sal_Bool SAL_CALL isAccessibleRowSelected( sal_Int32 nRow ) override; + virtual sal_Bool SAL_CALL isAccessibleColumnSelected( sal_Int32 nColumn ) override; + virtual css::uno::Reference< + css::accessibility::XAccessible > SAL_CALL + getAccessibleCellAt( sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual css::uno::Reference< + css::accessibility::XAccessible > SAL_CALL + getAccessibleCaption( ) override; + virtual css::uno::Reference< + css::accessibility::XAccessible > SAL_CALL + getAccessibleSummary( ) override; + virtual sal_Bool SAL_CALL isAccessibleSelected( + sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndex( + sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual sal_Int32 SAL_CALL getAccessibleRow( sal_Int32 nChildIndex ) override; + virtual sal_Int32 SAL_CALL getAccessibleColumn( sal_Int32 nChildIndex ) override; + // XAccessibleTableSelection + virtual sal_Bool SAL_CALL selectRow( sal_Int32 row ) override ; + virtual sal_Bool SAL_CALL selectColumn( sal_Int32 column ) override ; + virtual sal_Bool SAL_CALL unselectRow( sal_Int32 row ) override; + virtual sal_Bool SAL_CALL unselectColumn( sal_Int32 column ) override; + // XServiceInfo + + /** Returns an identifier for the implementation of this object. + */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. + */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. + */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // C++ interface + + // The object has been moved by the layout + virtual void InvalidatePosOrSize( const SwRect& rOldBox ) override; + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + + virtual void DisposeChild( const sw::access::SwAccessibleChild& rFrameOrObj, + bool bRecursive, bool bCanSkipInvisible ) override; + virtual void InvalidateChildPosOrSize( const sw::access::SwAccessibleChild& rFrameOrObj, + const SwRect& rFrame ) override; + + // XAccessibleSelection + + virtual void SAL_CALL selectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( + sal_Int32 nChildIndex ) override; + + virtual void SAL_CALL clearAccessibleSelection( ) override; + + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + // index has to be treated as global child index. + virtual void SAL_CALL deselectAccessibleChild( + sal_Int32 nChildIndex ) override; + + // XAccessibleComponent + sal_Int32 SAL_CALL getBackground() override; + typedef std::vector< std::pair<SwAccessibleContext*, + css::uno::WeakReference<css::accessibility::XAccessible> > > Cells_t; + Cells_t m_vecCellAdd; + Cells_t m_vecCellRemove; + void FireSelectionEvent( ); + void AddSelectionCell(SwAccessibleContext*, bool bAddOrRemove); +}; + +inline SwAccessibleTableData_Impl& SwAccessibleTable::GetTableData() +{ + if( !mpTableData ) + UpdateTableData(); + return *mpTableData; +} + +// #i77106# - subclass to represent table column headers +class SwAccessibleTableColHeaders : public SwAccessibleTable +{ +protected: + virtual ~SwAccessibleTableColHeaders() override + {} + + virtual std::unique_ptr<SwAccessibleTableData_Impl> CreateNewTableData() override; + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleTableColHeaders(std::shared_ptr<SwAccessibleMap> const& pMap, + const SwTabFrame *pTabFrame); + + // XInterface + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + // XAccessibleContext + + /// Return the number of currently visible children. + virtual sal_Int32 SAL_CALL getAccessibleChildCount() override; + + /// Return the specified child or NULL if index is invalid. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleChild (sal_Int32 nIndex) override; + + // XAccessibleTable + + virtual css::uno::Reference< + css::accessibility::XAccessibleTable > + SAL_CALL getAccessibleRowHeaders( ) override; + virtual css::uno::Reference< + css::accessibility::XAccessibleTable > + SAL_CALL getAccessibleColumnHeaders( ) override; + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. + */ + virtual OUString SAL_CALL + getImplementationName() override; + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acctextframe.cxx b/sw/source/core/access/acctextframe.cxx new file mode 100644 index 000000000..a5d6d43df --- /dev/null +++ b/sw/source/core/access/acctextframe.cxx @@ -0,0 +1,319 @@ +/* -*- 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 . + */ + +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleRelation.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <frmfmt.hxx> +#include <flyfrm.hxx> +#include <accmap.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <hints.hxx> +#include "acctextframe.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +using utl::AccessibleRelationSetHelper; +using ::com::sun::star::accessibility::XAccessibleContext; + +SwAccessibleTextFrame::SwAccessibleTextFrame( + std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFlyFrame& rFlyFrame ) : + SwAccessibleFrameBase( pInitMap, AccessibleRole::TEXT_FRAME, &rFlyFrame ) +{ + const SwFlyFrameFormat* pFlyFrameFormat = rFlyFrame.GetFormat(); + msTitle = pFlyFrameFormat->GetObjTitle(); + + msDesc = pFlyFrameFormat->GetObjDescription(); + if ( msDesc.isEmpty() && + msTitle != GetName() ) + { + msDesc = msTitle; + } +} + +SwAccessibleTextFrame::~SwAccessibleTextFrame() +{ +} + +void SwAccessibleTextFrame::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + EndListeningAll(); + else if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacyModifyHint = static_cast<const sw::LegacyModifyHint*>(&rHint); + const sal_uInt16 nWhich = pLegacyModifyHint->GetWhich(); + const SwFlyFrame* pFlyFrame = static_cast<const SwFlyFrame*>(GetFrame()); + switch(nWhich) + { + // #i73249# + case RES_TITLE_CHANGED: + { + OUString sOldTitle, sNewTitle; + const SwStringMsgPoolItem *pOldItem = dynamic_cast<const SwStringMsgPoolItem*>(pLegacyModifyHint->m_pOld); + if(pOldItem) + sOldTitle = pOldItem->GetString(); + const SwStringMsgPoolItem *pNewItem = dynamic_cast<const SwStringMsgPoolItem*>(pLegacyModifyHint->m_pNew); + if(pNewItem) + sNewTitle = pNewItem->GetString(); + if(sOldTitle == sNewTitle) + break; + msTitle = sNewTitle; + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + aEvent.OldValue <<= sOldTitle; + aEvent.NewValue <<= msTitle; + FireAccessibleEvent( aEvent ); + + const SwFlyFrameFormat* pFlyFrameFormat = pFlyFrame->GetFormat(); + if(!pFlyFrameFormat || !pFlyFrameFormat->GetObjDescription().isEmpty()) + break; + [[fallthrough]]; + } + case RES_DESCRIPTION_CHANGED: + { + if(pFlyFrame) + { + const OUString sOldDesc(msDesc); + + const SwFlyFrameFormat* pFlyFrameFormat = pFlyFrame->GetFormat(); + const OUString& rDesc = pFlyFrameFormat->GetObjDescription(); + msDesc = rDesc; + if(msDesc.isEmpty() && msTitle != GetName()) + msDesc = msTitle; + + if(msDesc != sOldDesc) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED; + aEvent.OldValue <<= sOldDesc; + aEvent.NewValue <<= msDesc; + FireAccessibleEvent(aEvent); + } + } + } + } + } +} + +// XInterface + +css::uno::Any SAL_CALL + SwAccessibleTextFrame::queryInterface (const css::uno::Type & rType) +{ + css::uno::Any aReturn = SwAccessibleContext::queryInterface (rType); + if ( ! aReturn.hasValue()) + aReturn = ::cppu::queryInterface (rType, + static_cast< css::accessibility::XAccessibleSelection* >(this) + ); + return aReturn; +} + +void SAL_CALL + SwAccessibleTextFrame::acquire() + noexcept +{ + SwAccessibleContext::acquire (); +} + +void SAL_CALL + SwAccessibleTextFrame::release() + noexcept +{ + SwAccessibleContext::release (); +} + +// XAccessibleSelection + +void SAL_CALL SwAccessibleTextFrame::selectAccessibleChild( sal_Int32 ) +{ + SAL_WARN("sw.a11y", "<SwAccessibleTextFrame::selectAccessibleChild( sal_Int32 )> - missing implementation"); +} + +sal_Bool SAL_CALL SwAccessibleTextFrame::isAccessibleChildSelected( sal_Int32 nChildIndex ) +{ + SolarMutexGuard g; + + uno::Reference<XAccessible> xAcc = getAccessibleChild( nChildIndex ); + uno::Reference<XAccessibleContext> xContext; + if( xAcc.is() ) + xContext = xAcc->getAccessibleContext(); + + if( xContext.is() ) + { + if( xContext->getAccessibleRole() == AccessibleRole::PARAGRAPH ) + { + uno::Reference< css::accessibility::XAccessibleText > + xText(xAcc, uno::UNO_QUERY); + if( xText.is() ) + { + if( xText->getSelectionStart() >= 0 ) return true; + } + } + } + + return false; +} + +void SAL_CALL SwAccessibleTextFrame::clearAccessibleSelection( ) +{ + SAL_WARN("sw.a11y", "<SwAccessibleTextFrame::clearAccessibleSelection()> - missing implementation"); +} + +void SAL_CALL SwAccessibleTextFrame::selectAllAccessibleChildren( ) +{ + SAL_WARN("sw.a11y", "<SwAccessibleTextFrame::selectAllAccessibleChildren()> - missing implementation"); +} + +sal_Int32 SAL_CALL SwAccessibleTextFrame::getSelectedAccessibleChildCount() +{ + sal_Int32 nCount = 0; + sal_Int32 TotalCount = getAccessibleChildCount(); + for( sal_Int32 i = 0; i < TotalCount; i++ ) + if( isAccessibleChildSelected(i) ) nCount++; + + return nCount; +} + +uno::Reference<XAccessible> SAL_CALL SwAccessibleTextFrame::getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) +{ + SolarMutexGuard g; + + if ( nSelectedChildIndex > getSelectedAccessibleChildCount() ) + throw lang::IndexOutOfBoundsException(); + sal_Int32 i1, i2; + for( i1 = 0, i2 = 0; i1 < getAccessibleChildCount(); i1++ ) + if( isAccessibleChildSelected(i1) ) + { + if( i2 == nSelectedChildIndex ) + return getAccessibleChild( i1 ); + i2++; + } + return uno::Reference<XAccessible>(); +} + +void SAL_CALL SwAccessibleTextFrame::deselectAccessibleChild( sal_Int32 ) +{ + SAL_WARN("sw.a11y", "<SwAccessibleTextFrame::selectAllAccessibleChildren( sal_Int32 )> - missing implementation"); +} + +// #i73249# +OUString SAL_CALL SwAccessibleTextFrame::getAccessibleName() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if ( !msTitle.isEmpty() ) + { + return msTitle; + } + + return SwAccessibleFrameBase::getAccessibleName(); +} + +OUString SAL_CALL SwAccessibleTextFrame::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return msDesc; + +} + +OUString SAL_CALL SwAccessibleTextFrame::getImplementationName() +{ + return "com.sun.star.comp.Writer.SwAccessibleTextFrameView"; +} + +sal_Bool SAL_CALL SwAccessibleTextFrame::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleTextFrame::getSupportedServiceNames() +{ + return { "com.sun.star.text.AccessibleTextFrameView", sAccessibleServiceName }; +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleTextFrame::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XAccessibleRelationSet + +SwFlyFrame* SwAccessibleTextFrame::getFlyFrame() const +{ + SwFlyFrame* pFlyFrame = nullptr; + + const SwFrame* pFrame = GetFrame(); + assert(pFrame); + if( pFrame->IsFlyFrame() ) + { + pFlyFrame = static_cast<SwFlyFrame*>( const_cast<SwFrame*>( pFrame ) ); + } + + return pFlyFrame; +} + +AccessibleRelation SwAccessibleTextFrame::makeRelation( sal_Int16 nType, const SwFlyFrame* pFrame ) +{ + uno::Sequence<uno::Reference<XInterface> > aSequence { GetMap()->GetContext( pFrame ) }; + return AccessibleRelation( nType, aSequence ); +} + +uno::Reference<XAccessibleRelationSet> SAL_CALL SwAccessibleTextFrame::getAccessibleRelationSet( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // get the frame, and insert prev/next relations into helper + + rtl::Reference<AccessibleRelationSetHelper> pHelper = new AccessibleRelationSetHelper(); + + SwFlyFrame* pFlyFrame = getFlyFrame(); + assert(pFlyFrame); + + const SwFlyFrame* pPrevFrame = pFlyFrame->GetPrevLink(); + if( pPrevFrame != nullptr ) + pHelper->AddRelation( makeRelation( + AccessibleRelationType::CONTENT_FLOWS_FROM, pPrevFrame ) ); + + const SwFlyFrame* pNextFrame = pFlyFrame->GetNextLink(); + if( pNextFrame != nullptr ) + pHelper->AddRelation( makeRelation( + AccessibleRelationType::CONTENT_FLOWS_TO, pNextFrame ) ); + + return pHelper; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acctextframe.hxx b/sw/source/core/access/acctextframe.hxx new file mode 100644 index 000000000..050970f62 --- /dev/null +++ b/sw/source/core/access/acctextframe.hxx @@ -0,0 +1,116 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCTEXTFRAME_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCTEXTFRAME_HXX + +#include "accframebase.hxx" + +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> + +class SwFlyFrame; + +class SwAccessibleTextFrame : public SwAccessibleFrameBase, + public css::accessibility::XAccessibleSelection +{ +private: + // #i73249# + OUString msTitle; + OUString msDesc; + +protected: + virtual ~SwAccessibleTextFrame() override; + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleTextFrame(std::shared_ptr<SwAccessibleMap> const& pInitMap, + const SwFlyFrame& rFlyFrame); + + virtual css::uno::Any SAL_CALL queryInterface( + css::uno::Type const & rType ) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + // XAccessibleSelection + virtual void SAL_CALL selectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( + sal_Int32 nChildIndex ) override; + + virtual void SAL_CALL clearAccessibleSelection( ) override; + + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + virtual void SAL_CALL deselectAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + // XAccessibleContext + + // #i73249# - Return the object's current name. + virtual OUString SAL_CALL + getAccessibleName() override; + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. + */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. + */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. + */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleContext::getAccessibleRelationSet + + // text frame may have accessible relations to their + // predecessor/successor frames + +private: + // helper methods for getAccessibleRelationSet: + SwFlyFrame* getFlyFrame() const; + + css::accessibility::AccessibleRelation makeRelation( + sal_Int16 nType, const SwFlyFrame* pFrame ); + +public: + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet> SAL_CALL getAccessibleRelationSet() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/parachangetrackinginfo.cxx b/sw/source/core/access/parachangetrackinginfo.cxx new file mode 100644 index 000000000..0943dc15e --- /dev/null +++ b/sw/source/core/access/parachangetrackinginfo.cxx @@ -0,0 +1,205 @@ +/* -*- 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 . + */ + +#include "parachangetrackinginfo.hxx" + +#include <wrong.hxx> +#include <com/sun/star/text/TextMarkupType.hpp> +#include <osl/diagnose.h> + +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <ndtxt.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <docary.hxx> +#include <redline.hxx> + +#include <algorithm> + +namespace { + void initChangeTrackTextMarkupLists( const SwTextFrame& rTextFrame, + std::unique_ptr<SwWrongList>& opChangeTrackInsertionTextMarkupList, + std::unique_ptr<SwWrongList>& opChangeTrackDeletionTextMarkupList, + std::unique_ptr<SwWrongList>& opChangeTrackFormatChangeTextMarkupList ) + { + opChangeTrackInsertionTextMarkupList.reset( new SwWrongList( WRONGLIST_CHANGETRACKING ) ); + opChangeTrackDeletionTextMarkupList.reset( new SwWrongList( WRONGLIST_CHANGETRACKING ) ); + opChangeTrackFormatChangeTextMarkupList.reset( new SwWrongList( WRONGLIST_CHANGETRACKING ) ); + + if (!rTextFrame.GetTextNodeFirst()) + { + OSL_FAIL( "<initChangeTrackTextMarkupLists(..) - missing <SwTextNode> instance!" ); + return; + } + // sw_redlinehide: the first node is sufficient - there are only + // multiple ones in Hide case and the code below returns early then + const SwTextNode& rTextNode(*(rTextFrame.GetTextNodeFirst())); + + const IDocumentRedlineAccess& rIDocChangeTrack( rTextNode.getIDocumentRedlineAccess() ); + + if (!IDocumentRedlineAccess::IsShowChanges(rIDocChangeTrack.GetRedlineFlags()) + || rTextFrame.getRootFrame()->IsHideRedlines() + || rIDocChangeTrack.GetRedlineTable().empty()) + { + // nothing to do --> empty change track text markup lists. + return; + } + + const SwRedlineTable::size_type nIdxOfFirstRedlineForTextNode = + rIDocChangeTrack.GetRedlinePos( rTextNode, RedlineType::Any ); + if ( nIdxOfFirstRedlineForTextNode == SwRedlineTable::npos ) + { + // nothing to do --> empty change track text markup lists. + return; + } + + // sw_redlinehide: rely on the Hide early return above & cast + // TextFrameIndex to SwIndex directly + const sal_Int32 nTextFrameTextStartPos = rTextFrame.IsFollow() + ? sal_Int32(rTextFrame.GetOffset()) + : 0; + const sal_Int32 nTextFrameTextEndPos = rTextFrame.HasFollow() + ? sal_Int32(rTextFrame.GetFollow()->GetOffset()) + : rTextFrame.GetText().getLength(); + + // iteration over the redlines which overlap with the text node. + const SwRedlineTable& rRedlineTable = rIDocChangeTrack.GetRedlineTable(); + const SwRedlineTable::size_type nRedlineCount( rRedlineTable.size() ); + for ( SwRedlineTable::size_type nActRedline = nIdxOfFirstRedlineForTextNode; + nActRedline < nRedlineCount; + ++nActRedline) + { + const SwRangeRedline* pActRedline = rRedlineTable[ nActRedline ]; + if ( pActRedline->Start()->nNode > rTextNode.GetIndex() ) + { + break; + } + + sal_Int32 nTextNodeChangeTrackStart(COMPLETE_STRING); + sal_Int32 nTextNodeChangeTrackEnd(COMPLETE_STRING); + pActRedline->CalcStartEnd( rTextNode.GetIndex(), + nTextNodeChangeTrackStart, + nTextNodeChangeTrackEnd ); + if ( nTextNodeChangeTrackStart > nTextFrameTextEndPos || + nTextNodeChangeTrackEnd < nTextFrameTextStartPos ) + { + // Consider only redlines which overlap with the text frame's text. + continue; + } + + SwWrongList* pMarkupList( nullptr ); + switch ( pActRedline->GetType() ) + { + case RedlineType::Insert: + { + pMarkupList = opChangeTrackInsertionTextMarkupList.get(); + } + break; + case RedlineType::Delete: + { + pMarkupList = opChangeTrackDeletionTextMarkupList.get(); + } + break; + case RedlineType::Format: + { + pMarkupList = opChangeTrackFormatChangeTextMarkupList.get(); + } + break; + default: + { + // other types are not considered + } + } + if ( pMarkupList ) + { + const sal_Int32 nTextFrameChangeTrackStart = + std::max(nTextNodeChangeTrackStart, nTextFrameTextStartPos); + + const sal_Int32 nTextFrameChangeTrackEnd = + std::min(nTextNodeChangeTrackEnd, nTextFrameTextEndPos); + + pMarkupList->Insert( OUString(), nullptr, + nTextFrameChangeTrackStart, + nTextFrameChangeTrackEnd - nTextFrameChangeTrackStart, + pMarkupList->Count() ); + } + } // eof iteration over the redlines which overlap with the text node + } +} // eof anonymous namespace + +SwParaChangeTrackingInfo::SwParaChangeTrackingInfo( const SwTextFrame& rTextFrame ) + : mrTextFrame( rTextFrame ) +{ +} + +SwParaChangeTrackingInfo::~SwParaChangeTrackingInfo() +{ + reset(); +} + +void SwParaChangeTrackingInfo::reset() +{ + mpChangeTrackInsertionTextMarkupList.reset(); + mpChangeTrackDeletionTextMarkupList.reset(); + mpChangeTrackFormatChangeTextMarkupList.reset(); +} + +const SwWrongList* SwParaChangeTrackingInfo::getChangeTrackingTextMarkupList( const sal_Int32 nTextMarkupType ) +{ + SwWrongList* pChangeTrackingTextMarkupList = nullptr; + + if ( mpChangeTrackInsertionTextMarkupList == nullptr ) + { + OSL_ENSURE( mpChangeTrackDeletionTextMarkupList == nullptr, + "<SwParaChangeTrackingInfo::getChangeTrackingTextMarkupList(..) - <mpChangeTrackDeletionTextMarkupList> expected to be NULL." ); + OSL_ENSURE( mpChangeTrackFormatChangeTextMarkupList == nullptr, + "<SwParaChangeTrackingInfo::getChangeTrackingTextMarkupList(..) - <mpChangeTrackFormatChangeTextMarkupList> expected to be NULL." ); + initChangeTrackTextMarkupLists( mrTextFrame, + mpChangeTrackInsertionTextMarkupList, + mpChangeTrackDeletionTextMarkupList, + mpChangeTrackFormatChangeTextMarkupList ); + } + + switch ( nTextMarkupType ) + { + case css::text::TextMarkupType::TRACK_CHANGE_INSERTION: + { + pChangeTrackingTextMarkupList = mpChangeTrackInsertionTextMarkupList.get(); + } + break; + case css::text::TextMarkupType::TRACK_CHANGE_DELETION: + { + pChangeTrackingTextMarkupList = mpChangeTrackDeletionTextMarkupList.get(); + } + break; + case css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE: + { + pChangeTrackingTextMarkupList = mpChangeTrackFormatChangeTextMarkupList.get(); + } + break; + default: + { + OSL_FAIL( "<SwParaChangeTrackingInfo::getChangeTrackingTextMarkupList(..)> - misusage - unexpected text markup type for change tracking." ); + } + } + + return pChangeTrackingTextMarkupList; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/parachangetrackinginfo.hxx b/sw/source/core/access/parachangetrackinginfo.hxx new file mode 100644 index 000000000..30f020125 --- /dev/null +++ b/sw/source/core/access/parachangetrackinginfo.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_PARACHANGETRACKINGINFO_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_PARACHANGETRACKINGINFO_HXX + +#include <sal/types.h> +#include <memory> + +class SwTextFrame; +class SwWrongList; + +class SwParaChangeTrackingInfo +{ + public: + explicit SwParaChangeTrackingInfo( const SwTextFrame& rTextFrame ); + ~SwParaChangeTrackingInfo(); + + void reset(); + + const SwWrongList* getChangeTrackingTextMarkupList( const sal_Int32 nTextMarkupType ); + + private: + SwParaChangeTrackingInfo( const SwParaChangeTrackingInfo& ) = delete; + SwParaChangeTrackingInfo& operator=( const SwParaChangeTrackingInfo& ) = delete; + + const SwTextFrame& mrTextFrame; + + std::unique_ptr<SwWrongList> mpChangeTrackInsertionTextMarkupList; + std::unique_ptr<SwWrongList> mpChangeTrackDeletionTextMarkupList; + std::unique_ptr<SwWrongList> mpChangeTrackFormatChangeTextMarkupList; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/textmarkuphelper.cxx b/sw/source/core/access/textmarkuphelper.cxx new file mode 100644 index 000000000..5329241c6 --- /dev/null +++ b/sw/source/core/access/textmarkuphelper.cxx @@ -0,0 +1,221 @@ +/* -*- 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 . + */ + +#include "textmarkuphelper.hxx" +#include "accportions.hxx" + +#include <vector> + +#include <com/sun/star/text/TextMarkupType.hpp> +#include <com/sun/star/accessibility/TextSegment.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +#include <comphelper/sequence.hxx> +#include <osl/diagnose.h> +#include <ndtxt.hxx> +#include <wrong.hxx> + +using namespace com::sun::star; + +// helper functions +namespace { + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + SwWrongList const* (SwTextNode::* + getTextMarkupFunc(const sal_Int32 nTextMarkupType))() const + { + switch ( nTextMarkupType ) + { + case text::TextMarkupType::SPELLCHECK: + { + return &SwTextNode::GetWrong; + } + break; + case text::TextMarkupType::PROOFREADING: + { + // support not implemented yet + return nullptr; + } + break; + case text::TextMarkupType::SMARTTAG: + { + // support not implemented yet + return nullptr; + } + break; + default: + { + throw lang::IllegalArgumentException(); + } + } + } +} + +// implementation of class <SwTextMarkupoHelper> +SwTextMarkupHelper::SwTextMarkupHelper( const SwAccessiblePortionData& rPortionData, + const SwTextFrame& rTextFrame) + : mrPortionData( rPortionData ) + , m_pTextFrame(&rTextFrame) + , mpTextMarkupList( nullptr ) +{ +} + +// #i108125# +SwTextMarkupHelper::SwTextMarkupHelper( const SwAccessiblePortionData& rPortionData, + const SwWrongList& rTextMarkupList ) + : mrPortionData( rPortionData ) + , m_pTextFrame( nullptr ) + , mpTextMarkupList( &rTextMarkupList ) +{ +} + +sal_Int32 SwTextMarkupHelper::getTextMarkupCount( const sal_Int32 nTextMarkupType ) +{ + sal_Int32 nTextMarkupCount( 0 ); + + if (mpTextMarkupList) + { + nTextMarkupCount = mpTextMarkupList->Count(); + } + else + { + assert(m_pTextFrame); + SwWrongList const* (SwTextNode::*const pGetWrongList)() const = getTextMarkupFunc(nTextMarkupType); + if (pGetWrongList) + { + sw::WrongListIteratorCounter iter(*m_pTextFrame, pGetWrongList); + nTextMarkupCount = iter.GetElementCount(); + } + } + + return nTextMarkupCount; +} + +css::accessibility::TextSegment + SwTextMarkupHelper::getTextMarkup( const sal_Int32 nTextMarkupIndex, + const sal_Int32 nTextMarkupType ) +{ + if ( nTextMarkupIndex >= getTextMarkupCount( nTextMarkupType ) || + nTextMarkupIndex < 0 ) + { + throw lang::IndexOutOfBoundsException(); + } + + css::accessibility::TextSegment aTextMarkupSegment; + aTextMarkupSegment.SegmentStart = -1; + aTextMarkupSegment.SegmentEnd = -1; + + std::unique_ptr<sw::WrongListIteratorCounter> pIter; + if (mpTextMarkupList) + { + pIter.reset(new sw::WrongListIteratorCounter(*mpTextMarkupList)); + } + else + { + assert(m_pTextFrame); + SwWrongList const* (SwTextNode::*const pGetWrongList)() const = getTextMarkupFunc(nTextMarkupType); + if (pGetWrongList) + { + pIter.reset(new sw::WrongListIteratorCounter(*m_pTextFrame, pGetWrongList)); + } + } + + if (pIter) + { + auto const oElement(pIter->GetElementAt(nTextMarkupIndex)); + if (oElement) + { + const OUString& rText = mrPortionData.GetAccessibleString(); + const sal_Int32 nStartPos = + mrPortionData.GetAccessiblePosition(oElement->first); + const sal_Int32 nEndPos = + mrPortionData.GetAccessiblePosition(oElement->second); + aTextMarkupSegment.SegmentText = rText.copy( nStartPos, nEndPos - nStartPos ); + aTextMarkupSegment.SegmentStart = nStartPos; + aTextMarkupSegment.SegmentEnd = nEndPos; + } + else + { + OSL_FAIL( "<SwTextMarkupHelper::getTextMarkup(..)> - missing <SwWrongArea> instance" ); + } + } + + return aTextMarkupSegment; +} + +css::uno::Sequence< css::accessibility::TextSegment > + SwTextMarkupHelper::getTextMarkupAtIndex( const sal_Int32 nCharIndex, + const sal_Int32 nTextMarkupType ) +{ + // assumption: + // value of <nCharIndex> is in range [0..length of accessible text) + + const TextFrameIndex nCoreCharIndex = mrPortionData.GetCoreViewPosition(nCharIndex); + // Handling of portions with core length == 0 at the beginning of the + // paragraph - e.g. numbering portion. + if ( mrPortionData.GetAccessiblePosition( nCoreCharIndex ) > nCharIndex ) + { + return uno::Sequence< css::accessibility::TextSegment >(); + } + + std::unique_ptr<sw::WrongListIteratorCounter> pIter; + if (mpTextMarkupList) + { + pIter.reset(new sw::WrongListIteratorCounter(*mpTextMarkupList)); + } + else + { + assert(m_pTextFrame); + SwWrongList const* (SwTextNode::*const pGetWrongList)() const = getTextMarkupFunc(nTextMarkupType); + if (pGetWrongList) + { + pIter.reset(new sw::WrongListIteratorCounter(*m_pTextFrame, pGetWrongList)); + } + } + + std::vector< css::accessibility::TextSegment > aTmpTextMarkups; + if (pIter) + { + const OUString& rText = mrPortionData.GetAccessibleString(); + sal_uInt16 count(pIter->GetElementCount()); + for (sal_uInt16 i = 0; i < count; ++i) + { + auto const oElement(pIter->GetElementAt(i)); + if (oElement && + oElement->first <= nCoreCharIndex && + nCoreCharIndex < oElement->second) + { + const sal_Int32 nStartPos = + mrPortionData.GetAccessiblePosition(oElement->first); + const sal_Int32 nEndPos = + mrPortionData.GetAccessiblePosition(oElement->second); + css::accessibility::TextSegment aTextMarkupSegment; + aTextMarkupSegment.SegmentText = rText.copy( nStartPos, nEndPos - nStartPos ); + aTextMarkupSegment.SegmentStart = nStartPos; + aTextMarkupSegment.SegmentEnd = nEndPos; + aTmpTextMarkups.push_back( aTextMarkupSegment ); + } + } + } + + return comphelper::containerToSequence(aTmpTextMarkups ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/textmarkuphelper.hxx b/sw/source/core/access/textmarkuphelper.hxx new file mode 100644 index 000000000..6db3f9bc0 --- /dev/null +++ b/sw/source/core/access/textmarkuphelper.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_TEXTMARKUPHELPER_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_TEXTMARKUPHELPER_HXX + +#include <sal/types.h> +#include <com/sun/star/uno/Sequence.h> + +namespace com::sun::star::accessibility { + struct TextSegment; +} + +class SwAccessiblePortionData; +class SwTextFrame; +class SwWrongList; // #i108125# + +class SwTextMarkupHelper +{ + public: + SwTextMarkupHelper( const SwAccessiblePortionData& rPortionData, + const SwTextFrame& rTextFrame); + SwTextMarkupHelper( const SwAccessiblePortionData& rPortionData, + const SwWrongList& rTextMarkupList ); // #i108125# + + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + sal_Int32 getTextMarkupCount( const sal_Int32 nTextMarkupType ); + + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + css::accessibility::TextSegment getTextMarkup( + const sal_Int32 nTextMarkupIndex, + const sal_Int32 nTextMarkupType ); + + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + css::uno::Sequence< css::accessibility::TextSegment > + getTextMarkupAtIndex( const sal_Int32 nCharIndex, + const sal_Int32 nTextMarkupType ); + + private: + SwTextMarkupHelper( const SwTextMarkupHelper& ) = delete; + SwTextMarkupHelper& operator=( const SwTextMarkupHelper& ) = delete; + + const SwAccessiblePortionData& mrPortionData; + + SwTextFrame const* m_pTextFrame; + const SwWrongList* mpTextMarkupList; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |