diff options
Diffstat (limited to 'sc/source/ui/Accessibility')
18 files changed, 13177 insertions, 0 deletions
diff --git a/sc/source/ui/Accessibility/AccessibilityHints.cxx b/sc/source/ui/Accessibility/AccessibilityHints.cxx new file mode 100644 index 0000000000..733311f721 --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibilityHints.cxx @@ -0,0 +1,62 @@ +/* -*- 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 <AccessibilityHints.hxx> + +using namespace ::com::sun::star; + +// ScAccWinFocusLostHint - the current window lost its focus (to another application, view or document) + +ScAccWinFocusLostHint::~ScAccWinFocusLostHint() +{ +} + +// ScAccWinFocusGotHint - the window got the focus (from another application, view or document) + +ScAccWinFocusGotHint::~ScAccWinFocusGotHint() +{ +} + +// ScAccGridWinFocusLostHint - the current grid window lost its focus (to another application, view or document) + +ScAccGridWinFocusLostHint::ScAccGridWinFocusLostHint(ScSplitPos eOld ) + : + ScAccWinFocusLostHint(), + eOldGridWin(eOld) +{ +} + +ScAccGridWinFocusLostHint::~ScAccGridWinFocusLostHint() +{ +} + +// ScAccGridWinFocusGotHint - the grid window got the focus (from another application, view or document) + +ScAccGridWinFocusGotHint::ScAccGridWinFocusGotHint(ScSplitPos eNew ) + : + ScAccWinFocusGotHint(), + eNewGridWin(eNew) +{ +} + +ScAccGridWinFocusGotHint::~ScAccGridWinFocusGotHint() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleCell.cxx b/sc/source/ui/Accessibility/AccessibleCell.cxx new file mode 100644 index 0000000000..3ac2fdba30 --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleCell.cxx @@ -0,0 +1,615 @@ +/* -*- 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 <string_view> + +#include <sal/config.h> + +#include <AccessibleCell.hxx> +#include <scitems.hxx> +#include <AccessibleText.hxx> +#include <AccessibleDocument.hxx> +#include <tabvwsh.hxx> +#include <comphelper/sequence.hxx> +#include <document.hxx> +#include <attrib.hxx> +#include <editsrc.hxx> +#include <dociter.hxx> +#include <markdata.hxx> +#include <cellvalue.hxx> +#include <formulaiter.hxx> +#include <validat.hxx> + +#include <unotools/accessiblerelationsethelper.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/accessibility/XAccessibleTable.hpp> +#include <editeng/brushitem.hxx> +#include <vcl/svapp.hxx> + +#include <AccessibleSpreadsheet.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +rtl::Reference<ScAccessibleCell> ScAccessibleCell::create( + const uno::Reference<XAccessible>& rxParent, + ScTabViewShell* pViewShell, + const ScAddress& rCellAddress, + sal_Int64 nIndex, + ScSplitPos eSplitPos, + ScAccessibleDocument* pAccDoc) +{ + rtl::Reference<ScAccessibleCell> x(new ScAccessibleCell( + rxParent, pViewShell, rCellAddress, nIndex, eSplitPos, pAccDoc)); + x->Init(); + return x; +} + +ScAccessibleCell::ScAccessibleCell( + const uno::Reference<XAccessible>& rxParent, + ScTabViewShell* pViewShell, + const ScAddress& rCellAddress, + sal_Int64 nIndex, + ScSplitPos eSplitPos, + ScAccessibleDocument* pAccDoc) + : + ScAccessibleCellBase(rxParent, GetDocument(pViewShell), rCellAddress, nIndex), + ::accessibility::AccessibleStaticTextBase(CreateEditSource(pViewShell, rCellAddress, eSplitPos)), + mpViewShell(pViewShell), + mpAccDoc(pAccDoc), + meSplitPos(eSplitPos) +{ + if (pViewShell) + pViewShell->AddAccessibilityObject(*this); +} + +ScAccessibleCell::~ScAccessibleCell() +{ + if (!ScAccessibleContextBase::IsDefunc() && !rBHelper.bInDispose) + { + // increment refcount to prevent double call off dtor + osl_atomic_increment( &m_refCount ); + // call dispose to inform object which have a weak reference to this object + dispose(); + } +} + +void ScAccessibleCell::Init() +{ + ScAccessibleCellBase::Init(); + + SetEventSource(this); +} + +void SAL_CALL ScAccessibleCell::disposing() +{ + SolarMutexGuard aGuard; + // dispose in AccessibleStaticTextBase + Dispose(); + + if (mpViewShell) + { + mpViewShell->RemoveAccessibilityObject(*this); + mpViewShell = nullptr; + } + mpAccDoc = nullptr; + + ScAccessibleCellBase::disposing(); +} + + //===== XInterface ===================================================== + +IMPLEMENT_FORWARD_XINTERFACE3( ScAccessibleCell, ScAccessibleCellBase, AccessibleStaticTextBase, ScAccessibleCellAttributeImpl ) + + //===== XTypeProvider =================================================== + +css::uno::Sequence< css::uno::Type > SAL_CALL ScAccessibleCell::getTypes() +{ + return ::comphelper::concatSequences( + ScAccessibleCellBase::getTypes(), + AccessibleStaticTextBase::getTypes(), + ScAccessibleCellAttributeImpl::getTypes() + ); +} +IMPLEMENT_GET_IMPLEMENTATION_ID( ScAccessibleCell ) + + //===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessibleCell::getAccessibleAtPoint( + const awt::Point& rPoint ) +{ + return AccessibleStaticTextBase::getAccessibleAtPoint(rPoint); +} + +void SAL_CALL ScAccessibleCell::grabFocus( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (getAccessibleParent().is() && mpViewShell) + { + uno::Reference<XAccessibleComponent> xAccessibleComponent(getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY); + if (xAccessibleComponent.is()) + { + xAccessibleComponent->grabFocus(); + mpViewShell->SetCursor(maCellAddress.Col(), maCellAddress.Row()); + } + } +} + +AbsoluteScreenPixelRectangle ScAccessibleCell::GetBoundingBoxOnScreen() const +{ + AbsoluteScreenPixelRectangle aCellRect(GetBoundingBox()); + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindowByPos(meSplitPos); + if (pWindow) + { + AbsoluteScreenPixelRectangle aRect = pWindow->GetWindowExtentsAbsolute(); + aCellRect.Move(aRect.Left(), aRect.Top()); + } + } + return aCellRect; +} + +tools::Rectangle ScAccessibleCell::GetBoundingBox() const +{ + tools::Rectangle aCellRect; + if (mpViewShell) + { + tools::Long nSizeX, nSizeY; + mpViewShell->GetViewData().GetMergeSizePixel( + maCellAddress.Col(), maCellAddress.Row(), nSizeX, nSizeY); + aCellRect.SetSize(Size(nSizeX, nSizeY)); + aCellRect.SetPos(mpViewShell->GetViewData().GetScrPos(maCellAddress.Col(), maCellAddress.Row(), meSplitPos, true)); + + vcl::Window* pWindow = mpViewShell->GetWindowByPos(meSplitPos); + if (pWindow) + { + tools::Rectangle aRect(pWindow->GetWindowExtentsRelative(*pWindow->GetAccessibleParentWindow())); + aRect.Move(-aRect.Left(), -aRect.Top()); + aCellRect = aRect.Intersection(aCellRect); + } + + /* #i19430# Gnopernicus reads text partly if it sticks out of the cell + boundaries. This leads to wrong results in cases where the cell + text is rotated, because rotation is not taken into account when + calculating the visible part of the text. In these cases we will + simply expand the cell size to the width of the unrotated text. */ + if (mpDoc) + { + const ScRotateValueItem* pItem = mpDoc->GetAttr( maCellAddress, ATTR_ROTATE_VALUE ); + if( pItem && (pItem->GetValue() != 0_deg100) ) + { + tools::Rectangle aParaRect = GetParagraphBoundingBox(); + if( !aParaRect.IsEmpty() && (aCellRect.GetWidth() < aParaRect.GetWidth()) ) + aCellRect.SetSize( Size( aParaRect.GetWidth(), aCellRect.GetHeight() ) ); + } + } + } + if (aCellRect.IsEmpty()) + aCellRect.SetPos(Point(-1, -1)); + return aCellRect; +} + + //===== XAccessibleContext ============================================== + +sal_Int64 SAL_CALL + ScAccessibleCell::getAccessibleChildCount() +{ + return AccessibleStaticTextBase::getAccessibleChildCount(); +} + +uno::Reference< XAccessible > SAL_CALL + ScAccessibleCell::getAccessibleChild(sal_Int64 nIndex) +{ + return AccessibleStaticTextBase::getAccessibleChild(nIndex); +} + +sal_Int64 SAL_CALL + ScAccessibleCell::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + sal_Int64 nStateSet = 0; + if (IsDefunc(nParentStates)) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + if (IsFocused()) + nStateSet |= AccessibleStateType::FOCUSED; + + if (IsFormulaMode()) + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::MULTI_LINE; + nStateSet |= AccessibleStateType::MULTI_SELECTABLE; + if (IsOpaque()) + nStateSet |= AccessibleStateType::OPAQUE; + nStateSet |= AccessibleStateType::SELECTABLE; + if (IsSelected()) + nStateSet |= AccessibleStateType::SELECTED; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + nStateSet |= AccessibleStateType::TRANSIENT; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + return nStateSet; + } + if (IsEditable(nParentStates)) + { + nStateSet |= AccessibleStateType::EDITABLE; + nStateSet |= AccessibleStateType::RESIZABLE; + } + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::MULTI_LINE; + nStateSet |= AccessibleStateType::MULTI_SELECTABLE; + nStateSet |= AccessibleStateType::FOCUSABLE; + if (IsOpaque()) + nStateSet |= AccessibleStateType::OPAQUE; + nStateSet |= AccessibleStateType::SELECTABLE; + if (IsSelected()) + nStateSet |= AccessibleStateType::SELECTED; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + nStateSet |= AccessibleStateType::TRANSIENT; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + } + return nStateSet; +} + +uno::Reference<XAccessibleRelationSet> SAL_CALL + ScAccessibleCell::getAccessibleRelationSet() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSet; + if (mpAccDoc) + pRelationSet = mpAccDoc->GetRelationSet(&maCellAddress); + if (!pRelationSet) + pRelationSet = new utl::AccessibleRelationSetHelper(); + FillDependents(pRelationSet.get()); + FillPrecedents(pRelationSet.get()); + return pRelationSet; +} + + //===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessibleCell::getImplementationName() +{ + return "ScAccessibleCell"; +} + +uno::Sequence< OUString> SAL_CALL + ScAccessibleCell::getSupportedServiceNames() +{ + const css::uno::Sequence<OUString> vals { "com.sun.star.sheet.AccessibleCell" }; + return comphelper::concatSequences(ScAccessibleContextBase::getSupportedServiceNames(), vals); +} + + //==== internal ========================================================= + +bool ScAccessibleCell::IsDefunc(sal_Int64 nParentStates) +{ + return ScAccessibleContextBase::IsDefunc() || (mpDoc == nullptr) || (mpViewShell == nullptr) || !getAccessibleParent().is() || + (nParentStates & AccessibleStateType::DEFUNC); +} + +bool ScAccessibleCell::IsEditable(sal_Int64 nParentStates) +{ + bool bEditable(true); + if ( !(nParentStates & AccessibleStateType::EDITABLE) && + mpDoc) + { + // here I have to test whether the protection of the table should influence this cell. + const ScProtectionAttr* pItem = mpDoc->GetAttr(maCellAddress, ATTR_PROTECTION); + if (pItem) + bEditable = !pItem->GetProtection(); + } + return bEditable; +} + +bool ScAccessibleCell::IsOpaque() const +{ + // test whether there is a background color + bool bOpaque(true); + if (mpDoc) + { + const SvxBrushItem* pItem = mpDoc->GetAttr(maCellAddress, ATTR_BACKGROUND); + if (pItem) + bOpaque = pItem->GetColor() != COL_TRANSPARENT; + } + return bOpaque; +} + +bool ScAccessibleCell::IsFocused() const +{ + if (mpViewShell && mpViewShell->GetViewData().GetCurPos() == maCellAddress) + return mpViewShell->GetActiveWin()->HasFocus(); + + return false; +} + +bool ScAccessibleCell::IsSelected() +{ + if (IsFormulaMode()) + { + const ScAccessibleSpreadsheet *pSheet =static_cast<const ScAccessibleSpreadsheet*>(mxParent.get()); + if (pSheet) + { + return pSheet->IsScAddrFormulaSel(maCellAddress); + } + return false; + } + + bool bResult(false); + if (mpViewShell) + { + const ScMarkData& rMarkdata = mpViewShell->GetViewData().GetMarkData(); + bResult = rMarkdata.IsCellMarked(maCellAddress.Col(), maCellAddress.Row()); + } + return bResult; +} + +ScDocument* ScAccessibleCell::GetDocument(ScTabViewShell* pViewShell) +{ + ScDocument* pDoc = nullptr; + if (pViewShell) + pDoc = &pViewShell->GetViewData().GetDocument(); + return pDoc; +} + +::std::unique_ptr< SvxEditSource > ScAccessibleCell::CreateEditSource(ScTabViewShell* pViewShell, ScAddress aCell, ScSplitPos eSplitPos) +{ + if (IsFormulaMode()) + { + return ::std::unique_ptr< SvxEditSource >(); + } + ::std::unique_ptr< SvxEditSource > pEditSource (new ScAccessibilityEditSource(std::make_unique<ScAccessibleCellTextData>(pViewShell, aCell, eSplitPos, this))); + + return pEditSource; +} + +void ScAccessibleCell::FillDependents(utl::AccessibleRelationSetHelper* pRelationSet) +{ + if (!mpDoc) + return; + + ScRange aRange(0, 0, maCellAddress.Tab(), mpDoc->MaxCol(), mpDoc->MaxRow(), maCellAddress.Tab()); + ScCellIterator aCellIter(*mpDoc, aRange); + + for (bool bHasCell = aCellIter.first(); bHasCell; bHasCell = aCellIter.next()) + { + if (aCellIter.getType() == CELLTYPE_FORMULA) + { + bool bFound = false; + ScDetectiveRefIter aIter(*mpDoc, aCellIter.getFormulaCell()); + ScRange aRef; + while ( !bFound && aIter.GetNextRef( aRef ) ) + { + if (aRef.Contains(maCellAddress)) + bFound = true; + } + if (bFound) + AddRelation(aCellIter.GetPos(), AccessibleRelationType::CONTROLLER_FOR, pRelationSet); + } + } +} + +void ScAccessibleCell::FillPrecedents(utl::AccessibleRelationSetHelper* pRelationSet) +{ + if (!mpDoc) + return; + + ScRefCellValue aCell(*mpDoc, maCellAddress); + if (aCell.getType() == CELLTYPE_FORMULA) + { + ScFormulaCell* pCell = aCell.getFormula(); + ScDetectiveRefIter aIter(*mpDoc, pCell); + ScRange aRef; + while ( aIter.GetNextRef( aRef ) ) + { + AddRelation( aRef, AccessibleRelationType::CONTROLLED_BY, pRelationSet); + } + } +} + +void ScAccessibleCell::AddRelation(const ScAddress& rCell, + const sal_uInt16 aRelationType, + utl::AccessibleRelationSetHelper* pRelationSet) +{ + AddRelation(ScRange(rCell, rCell), aRelationType, pRelationSet); +} + +void ScAccessibleCell::AddRelation(const ScRange& rRange, + const sal_uInt16 aRelationType, + utl::AccessibleRelationSetHelper* pRelationSet) +{ + uno::Reference < XAccessibleTable > xTable ( getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY ); + if (!xTable.is()) + return; + + const sal_uInt32 nCount(static_cast<sal_uInt32>(rRange.aEnd.Col() - + rRange.aStart.Col() + 1) * (rRange.aEnd.Row() - + rRange.aStart.Row() + 1)); + + // tdf#157299 avoid handling a large amount of cells for performance reasons + if (nCount > 1000) + { + SAL_WARN("sc", "ScAccessibleCell::AddRelation: Not setting relations " + "for cell range with more than 1000 cells for performance reasons."); + return; + } + + uno::Sequence < uno::Reference < uno::XInterface > > aTargetSet( nCount ); + uno::Reference < uno::XInterface >* pTargetSet = aTargetSet.getArray(); + sal_uInt32 nPos(0); + for (sal_uInt32 nRow = rRange.aStart.Row(); nRow <= sal::static_int_cast<sal_uInt32>(rRange.aEnd.Row()); ++nRow) + { + for (sal_uInt32 nCol = rRange.aStart.Col(); nCol <= sal::static_int_cast<sal_uInt32>(rRange.aEnd.Col()); ++nCol) + { + pTargetSet[nPos] = xTable->getAccessibleCellAt(nRow, nCol); + ++nPos; + } + } + OSL_ENSURE(nCount == nPos, "something went wrong"); + AccessibleRelation aRelation; + aRelation.RelationType = aRelationType; + aRelation.TargetSet = aTargetSet; + pRelationSet->AddRelation(aRelation); +} + +static OUString ReplaceFourChar(const OUString& oldOUString) +{ + return oldOUString.replaceAll(u"\\", u"\\\\") + .replaceAll(u";", u"\\;") + .replaceAll(u"=", u"\\=") + .replaceAll(u",", u"\\,") + .replaceAll(u":", u"\\:"); +} + +uno::Any SAL_CALL ScAccessibleCell::getExtendedAttributes() +{ + SolarMutexGuard aGuard; + + // report row and column index text via attributes as specified in ARIA which map + // to attributes of the same name for AT-SPI2, IAccessible2, UIA + // https://www.w3.org/TR/core-aam-1.2/#ariaRowIndexText + // https://www.w3.org/TR/core-aam-1.2/#ariaColIndexText + const OUString sRowIndexText = maCellAddress.Format(ScRefFlags::ROW_VALID); + const OUString sColIndexText = maCellAddress.Format(ScRefFlags::COL_VALID); + OUString sAttributes = "rowindextext:" + sRowIndexText + ";colindextext:" + sColIndexText + ";"; + + if (mpViewShell) + { + OUString strFor = mpViewShell->GetFormula(maCellAddress) ; + if (!strFor.isEmpty()) + { + strFor = strFor.copy(1); + strFor = ReplaceFourChar(strFor); + } + strFor = "Formula:" + strFor + + ";Note:" + + ReplaceFourChar(GetAllDisplayNote()) + ";" + + getShadowAttrs() + //the string returned contains the spliter ";" + getBorderAttrs();//the string returned contains the spliter ";" + //end of cell attributes + if( mpDoc ) + { + strFor += "isdropdown:"; + if( IsDropdown() ) + strFor += "true"; + else + strFor += "false"; + strFor += ";"; + } + sAttributes += strFor ; + } + + return uno::Any(sAttributes); +} + +// cell has its own ParaIndent property, so when calling character attributes on cell, the ParaIndent should replace the ParaLeftMargin if its value is not zero. +uno::Sequence< beans::PropertyValue > SAL_CALL ScAccessibleCell::getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) +{ + SolarMutexGuard aGuard; + + uno::Sequence< beans::PropertyValue > aAttribs = AccessibleStaticTextBase::getCharacterAttributes( nIndex, aRequestedAttributes ); + + sal_uInt16 nParaIndent = mpDoc->GetAttr( maCellAddress, ATTR_INDENT )->GetValue(); + if (nParaIndent > 0) + { + auto [begin, end] = asNonConstRange(aAttribs); + auto pAttrib = std::find_if(begin, end, + [](const beans::PropertyValue& rAttrib) { return "ParaLeftMargin" == rAttrib.Name; }); + if (pAttrib != end) + pAttrib->Value <<= nParaIndent; + } + return aAttribs; +} + +bool ScAccessibleCell::IsFormulaMode() +{ + ScAccessibleSpreadsheet* pSheet = static_cast<ScAccessibleSpreadsheet*>(mxParent.get()); + if (pSheet) + { + return pSheet->IsFormulaMode(); + } + return false; +} + +bool ScAccessibleCell::IsDropdown() const +{ + sal_uInt16 nPosX = maCellAddress.Col(); + sal_uInt16 nPosY = sal_uInt16(maCellAddress.Row()); + sal_uInt16 nTab = maCellAddress.Tab(); + sal_uInt32 nValidation = mpDoc->GetAttr( nPosX, nPosY, nTab, ATTR_VALIDDATA )->GetValue(); + if( nValidation ) + { + const ScValidationData* pData = mpDoc->GetValidationEntry( nValidation ); + if( pData && pData->HasSelectionList() ) + return true; + } + const ScMergeFlagAttr* pAttr = mpDoc->GetAttr( nPosX, nPosY, nTab, ATTR_MERGE_FLAG ); + if( pAttr->HasAutoFilter() ) + { + return true; + } + else + { + sal_uInt16 nTabCount = mpDoc->GetTableCount(); + if ( nTab+1<nTabCount && mpDoc->IsScenario(nTab+1) && !mpDoc->IsScenario(nTab) ) + { + SCTAB i; + ScMarkData aMarks(mpDoc->GetSheetLimits()); + for (i=nTab+1; i<nTabCount && mpDoc->IsScenario(i); i++) + mpDoc->MarkScenario( i, nTab, aMarks, false, ScScenarioFlags::ShowFrame ); + ScRangeList aRanges; + aMarks.FillRangeListWithMarks( &aRanges, false ); + bool bHasScenario; + SCTAB nRangeCount = aRanges.size(); + for (i=0; i<nRangeCount; i++) + { + ScRange aRange = aRanges[i]; + mpDoc->ExtendTotalMerge( aRange ); + bool bTextBelow = ( aRange.aStart.Row() == 0 ); + // MT IA2: Not used: sal_Bool bIsInScen = sal_False; + if ( bTextBelow ) + { + bHasScenario = (aRange.aStart.Col() == nPosX && aRange.aEnd.Row() == nPosY-1); + } + else + { + bHasScenario = (aRange.aStart.Col() == nPosX && aRange.aStart.Row() == nPosY+1); + } + if( bHasScenario ) return true; + } + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleCellBase.cxx b/sc/source/ui/Accessibility/AccessibleCellBase.cxx new file mode 100644 index 0000000000..d8b84fabaf --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleCellBase.cxx @@ -0,0 +1,587 @@ +/* -*- 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 <AccessibleCellBase.hxx> +#include <document.hxx> +#include <docfunc.hxx> +#include <docsh.hxx> +#include <strings.hxx> +#include <unonames.hxx> +#include <detfunc.hxx> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/sheet/XSheetAnnotationAnchor.hpp> +#include <com/sun/star/text/XSimpleText.hpp> +#include <com/sun/star/table/BorderLine.hpp> +#include <com/sun/star/table/ShadowFormat.hpp> +#include <comphelper/sequence.hxx> +#include <sfx2/objsh.hxx> +#include <vcl/svapp.hxx> + +#include <float.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +#define DEFAULT_LINE_WIDTH 2 + +//===== internal ============================================================ + +ScAccessibleCellBase::ScAccessibleCellBase( + const uno::Reference<XAccessible>& rxParent, + ScDocument* pDoc, + const ScAddress& rCellAddress, + sal_Int64 nIndex) + : + ScAccessibleContextBase(rxParent, AccessibleRole::TABLE_CELL), + maCellAddress(rCellAddress), + mpDoc(pDoc), + mnIndex(nIndex) +{ +} + +ScAccessibleCellBase::~ScAccessibleCellBase() +{ +} + + //===== XAccessibleComponent ============================================ + +bool ScAccessibleCellBase::isVisible() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + // test whether the cell is hidden (column/row - hidden/filtered) + bool bVisible(true); + if (mpDoc) + { + bool bColHidden = mpDoc->ColHidden(maCellAddress.Col(), maCellAddress.Tab()); + bool bRowHidden = mpDoc->RowHidden(maCellAddress.Row(), maCellAddress.Tab()); + bool bColFiltered = mpDoc->ColFiltered(maCellAddress.Col(), maCellAddress.Tab()); + bool bRowFiltered = mpDoc->RowFiltered(maCellAddress.Row(), maCellAddress.Tab()); + + if (bColHidden || bColFiltered || bRowHidden || bRowFiltered) + bVisible = false; + } + return bVisible; +} + +sal_Int32 SAL_CALL ScAccessibleCellBase::getForeground() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + sal_Int32 nColor(0); + if (mpDoc) + { + ScDocShell* pObjSh = mpDoc->GetDocumentShell(); + if ( pObjSh ) + { + ScModelObj* pSpreadDoc = pObjSh->GetModel(); + if ( pSpreadDoc ) + { + uno::Reference<sheet::XSpreadsheets> xSheets = pSpreadDoc->getSheets(); + uno::Reference<container::XIndexAccess> xIndex( xSheets, uno::UNO_QUERY ); + if ( xIndex.is() ) + { + uno::Any aTable = xIndex->getByIndex(maCellAddress.Tab()); + uno::Reference<sheet::XSpreadsheet> xTable; + if (aTable>>=xTable) + { + uno::Reference<table::XCell> xCell = xTable->getCellByPosition(maCellAddress.Col(), maCellAddress.Row()); + if (xCell.is()) + { + uno::Reference<beans::XPropertySet> xCellProps(xCell, uno::UNO_QUERY); + if (xCellProps.is()) + { + uno::Any aAny = xCellProps->getPropertyValue(SC_UNONAME_CCOLOR); + aAny >>= nColor; + } + } + } + } + } + } + } + return nColor; +} + +sal_Int32 SAL_CALL ScAccessibleCellBase::getBackground() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + sal_Int32 nColor(0); + + if (mpDoc) + { + ScDocShell* pObjSh = mpDoc->GetDocumentShell(); + if ( pObjSh ) + { + ScModelObj* pSpreadDoc = pObjSh->GetModel(); + if ( pSpreadDoc ) + { + uno::Reference<sheet::XSpreadsheets> xSheets = pSpreadDoc->getSheets(); + uno::Reference<container::XIndexAccess> xIndex( xSheets, uno::UNO_QUERY ); + if ( xIndex.is() ) + { + uno::Any aTable = xIndex->getByIndex(maCellAddress.Tab()); + uno::Reference<sheet::XSpreadsheet> xTable; + if (aTable>>=xTable) + { + uno::Reference<table::XCell> xCell = xTable->getCellByPosition(maCellAddress.Col(), maCellAddress.Row()); + if (xCell.is()) + { + uno::Reference<beans::XPropertySet> xCellProps(xCell, uno::UNO_QUERY); + if (xCellProps.is()) + { + uno::Any aAny = xCellProps->getPropertyValue(SC_UNONAME_CELLBACK); + aAny >>= nColor; + } + } + } + } + } + } + } + + return nColor; +} + + //===== XInterface ===================================================== + +uno::Any SAL_CALL ScAccessibleCellBase::queryInterface( uno::Type const & rType ) +{ + uno::Any aAny (ScAccessibleCellBaseImpl::queryInterface(rType)); + return aAny.hasValue() ? aAny : ScAccessibleContextBase::queryInterface(rType); +} + +void SAL_CALL ScAccessibleCellBase::acquire() + noexcept +{ + ScAccessibleContextBase::acquire(); +} + +void SAL_CALL ScAccessibleCellBase::release() + noexcept +{ + ScAccessibleContextBase::release(); +} + + //===== XAccessibleContext ============================================== + +sal_Int64 + ScAccessibleCellBase::getAccessibleIndexInParent() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return mnIndex; +} + +OUString + ScAccessibleCellBase::createAccessibleDescription() +{ + return STR_ACC_CELL_DESCR; +} + +OUString + ScAccessibleCellBase::createAccessibleName() +{ + // Document not needed, because only the cell address, but not the tablename is needed + // always us OOO notation + return maCellAddress.Format(ScRefFlags::VALID); +} + + //===== XAccessibleValue ================================================ + +uno::Any SAL_CALL + ScAccessibleCellBase::getCurrentValue() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + uno::Any aAny; + if (mpDoc) + { + aAny <<= mpDoc->GetValue(maCellAddress); + } + return aAny; +} + +sal_Bool SAL_CALL + ScAccessibleCellBase::setCurrentValue( const uno::Any& aNumber ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + double fValue = 0; + bool bResult = false; + if((aNumber >>= fValue) && mpDoc && mpDoc->GetDocumentShell()) + { + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + if (IsEditable(nParentStates)) + { + ScDocShell* pDocShell = mpDoc->GetDocumentShell(); + bResult = pDocShell->GetDocFunc().SetValueCell(maCellAddress, fValue, false); + } + } + return bResult; +} + +uno::Any SAL_CALL + ScAccessibleCellBase::getMaximumValue( ) +{ + return uno::Any(DBL_MAX); +} + +uno::Any SAL_CALL + ScAccessibleCellBase::getMinimumValue( ) +{ + return uno::Any(-DBL_MAX); +} + +uno::Any SAL_CALL + ScAccessibleCellBase::getMinimumIncrement( ) +{ + return uno::Any(); +} + + //===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessibleCellBase::getImplementationName() +{ + return "ScAccessibleCellBase"; +} + + //===== XTypeProvider =================================================== + +uno::Sequence< uno::Type > SAL_CALL ScAccessibleCellBase::getTypes() +{ + return comphelper::concatSequences(ScAccessibleCellBaseImpl::getTypes(), ScAccessibleContextBase::getTypes()); +} + +uno::Sequence<sal_Int8> SAL_CALL + ScAccessibleCellBase::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +bool ScAccessibleCellBase::IsEditable(sal_Int64 nParentStates) +{ + bool bEditable = nParentStates & AccessibleStateType::EDITABLE; + return bEditable; +} + +OUString ScAccessibleCellBase::GetNote() const +{ + SolarMutexGuard aGuard; + IsObjectValid(); + OUString sNote; + if (mpDoc) + { + ScDocShell* pObjSh = mpDoc->GetDocumentShell(); + if ( pObjSh ) + { + ScModelObj* pSpreadDoc = pObjSh->GetModel(); + if ( pSpreadDoc ) + { + uno::Reference<sheet::XSpreadsheets> xSheets = pSpreadDoc->getSheets(); + uno::Reference<container::XIndexAccess> xIndex( xSheets, uno::UNO_QUERY ); + if ( xIndex.is() ) + { + uno::Any aTable = xIndex->getByIndex(maCellAddress.Tab()); + uno::Reference<sheet::XSpreadsheet> xTable; + if (aTable>>=xTable) + { + uno::Reference<table::XCell> xCell = xTable->getCellByPosition(maCellAddress.Col(), maCellAddress.Row()); + if (xCell.is()) + { + uno::Reference <sheet::XSheetAnnotationAnchor> xAnnotationAnchor ( xCell, uno::UNO_QUERY); + if(xAnnotationAnchor.is()) + { + uno::Reference <sheet::XSheetAnnotation> xSheetAnnotation = xAnnotationAnchor->getAnnotation(); + if (xSheetAnnotation.is()) + { + uno::Reference <text::XSimpleText> xText (xSheetAnnotation, uno::UNO_QUERY); + if (xText.is()) + { + sNote = xText->getString(); + } + } + } + } + } + } + } + } + } + return sNote; +} + +OUString ScAccessibleCellBase::getShadowAttrs() const +{ + SolarMutexGuard aGuard; + IsObjectValid(); + table::ShadowFormat aShadowFmt; + if (mpDoc) + { + ScDocShell* pObjSh = mpDoc->GetDocumentShell(); + if ( pObjSh ) + { + ScModelObj* pSpreadDoc = pObjSh->GetModel(); + if ( pSpreadDoc ) + { + uno::Reference<sheet::XSpreadsheets> xSheets = pSpreadDoc->getSheets(); + uno::Reference<container::XIndexAccess> xIndex( xSheets, uno::UNO_QUERY ); + if ( xIndex.is() ) + { + uno::Any aTable = xIndex->getByIndex(maCellAddress.Tab()); + uno::Reference<sheet::XSpreadsheet> xTable; + if (aTable>>=xTable) + { + uno::Reference<table::XCell> xCell = xTable->getCellByPosition(maCellAddress.Col(), maCellAddress.Row()); + if (xCell.is()) + { + uno::Reference<beans::XPropertySet> xCellProps(xCell, uno::UNO_QUERY); + if (xCellProps.is()) + { + uno::Any aAny = xCellProps->getPropertyValue(SC_UNONAME_SHADOW); + aAny >>= aShadowFmt; + } + } + } + } + } + } + } + //construct shadow attributes string + OUString sShadowAttrs("Shadow:"); + OUString sInnerSplit(","); + OUString sOuterSplit(";"); + sal_Int32 nLocationVal = 0; + switch( aShadowFmt.Location ) + { + case table::ShadowLocation_TOP_LEFT: + nLocationVal = 1; + break; + case table::ShadowLocation_TOP_RIGHT: + nLocationVal = 2; + break; + case table::ShadowLocation_BOTTOM_LEFT: + nLocationVal = 3; + break; + case table::ShadowLocation_BOTTOM_RIGHT: + nLocationVal = 4; + break; + default: + break; + } + //if there is no shadow property for the cell + if ( nLocationVal == 0 ) + { + sShadowAttrs += sOuterSplit; + return sShadowAttrs; + } + //else return all the shadow properties + sShadowAttrs += "Location=" + + OUString::number( nLocationVal ) + + sInnerSplit + + "ShadowWidth=" + + OUString::number( static_cast<sal_Int32>(aShadowFmt.ShadowWidth) ) + + sInnerSplit + + "IsTransparent=" + + OUString::number( static_cast<int>(aShadowFmt.IsTransparent) ) + + sInnerSplit + + "Color=" + + OUString::number( aShadowFmt.Color ) + + sOuterSplit; + return sShadowAttrs; +} + +OUString ScAccessibleCellBase::getBorderAttrs() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + table::BorderLine aTopBorder; + table::BorderLine aBottomBorder; + table::BorderLine aLeftBorder; + table::BorderLine aRightBorder; + if (mpDoc) + { + ScDocShell* pObjSh = mpDoc->GetDocumentShell(); + if ( pObjSh ) + { + ScModelObj* pSpreadDoc = pObjSh->GetModel(); + if ( pSpreadDoc ) + { + uno::Reference<sheet::XSpreadsheets> xSheets = pSpreadDoc->getSheets(); + uno::Reference<container::XIndexAccess> xIndex( xSheets, uno::UNO_QUERY ); + if ( xIndex.is() ) + { + uno::Any aTable = xIndex->getByIndex(maCellAddress.Tab()); + uno::Reference<sheet::XSpreadsheet> xTable; + if (aTable>>=xTable) + { + uno::Reference<table::XCell> xCell = xTable->getCellByPosition(maCellAddress.Col(), maCellAddress.Row()); + if (xCell.is()) + { + uno::Reference<beans::XPropertySet> xCellProps(xCell, uno::UNO_QUERY); + if (xCellProps.is()) + { + uno::Any aAny = xCellProps->getPropertyValue(SC_UNONAME_TOPBORDER); + aAny >>= aTopBorder; + aAny = xCellProps->getPropertyValue(SC_UNONAME_BOTTBORDER); + aAny >>= aBottomBorder; + aAny = xCellProps->getPropertyValue(SC_UNONAME_LEFTBORDER); + aAny >>= aLeftBorder; + aAny = xCellProps->getPropertyValue(SC_UNONAME_RIGHTBORDER); + aAny >>= aRightBorder; + } + } + } + } + } + } + } + + Color aColor; + bool bIn = mpDoc && mpDoc->IsCellInChangeTrack(maCellAddress,&aColor); + if (bIn) + { + aTopBorder.Color = sal_Int32(aColor); + aBottomBorder.Color = sal_Int32(aColor); + aLeftBorder.Color = sal_Int32(aColor); + aRightBorder.Color = sal_Int32(aColor); + aTopBorder.OuterLineWidth = DEFAULT_LINE_WIDTH; + aBottomBorder.OuterLineWidth = DEFAULT_LINE_WIDTH; + aLeftBorder.OuterLineWidth = DEFAULT_LINE_WIDTH; + aRightBorder.OuterLineWidth = DEFAULT_LINE_WIDTH; + } + + //construct border attributes string + OUString sBorderAttrs; + OUString sInnerSplit(","); + OUString sOuterSplit(";"); + //top border + //if top of the cell has no border + if ( aTopBorder.InnerLineWidth == 0 && aTopBorder.OuterLineWidth == 0 ) + { + sBorderAttrs += "TopBorder:;"; + } + else//add all the border properties to the return string. + { + sBorderAttrs += "TopBorder:Color=" + + OUString::number( aTopBorder.Color ) + + sInnerSplit + + "InnerLineWidth=" + + OUString::number( static_cast<sal_Int32>(aTopBorder.InnerLineWidth) ) + + sInnerSplit + + "OuterLineWidth=" + + OUString::number( static_cast<sal_Int32>(aTopBorder.OuterLineWidth) ) + + sInnerSplit + + "LineDistance=" + + OUString::number( static_cast<sal_Int32>(aTopBorder.LineDistance) ) + + sOuterSplit; + } + //bottom border + if ( aBottomBorder.InnerLineWidth == 0 && aBottomBorder.OuterLineWidth == 0 ) + { + sBorderAttrs += "BottomBorder:;"; + } + else + { + sBorderAttrs += "BottomBorder:Color=" + + OUString::number( aBottomBorder.Color ) + + sInnerSplit + + "InnerLineWidth=" + + OUString::number( static_cast<sal_Int32>(aBottomBorder.InnerLineWidth) ) + + sInnerSplit + + "OuterLineWidth=" + + OUString::number( static_cast<sal_Int32>(aBottomBorder.OuterLineWidth) ) + + sInnerSplit + + "LineDistance=" + + OUString::number( static_cast<sal_Int32>(aBottomBorder.LineDistance) ) + + sOuterSplit; + } + //left border + if ( aLeftBorder.InnerLineWidth == 0 && aLeftBorder.OuterLineWidth == 0 ) + { + sBorderAttrs += "LeftBorder:;"; + } + else + { + sBorderAttrs += "LeftBorder:Color=" + + OUString::number( aLeftBorder.Color ) + + sInnerSplit + + "InnerLineWidth=" + + OUString::number( static_cast<sal_Int32>(aLeftBorder.InnerLineWidth) ) + + sInnerSplit + + "OuterLineWidth=" + + OUString::number( static_cast<sal_Int32>(aLeftBorder.OuterLineWidth) ) + + sInnerSplit + + "LineDistance=" + + OUString::number( static_cast<sal_Int32>(aLeftBorder.LineDistance) ) + + sOuterSplit; + } + //right border + if ( aRightBorder.InnerLineWidth == 0 && aRightBorder.OuterLineWidth == 0 ) + { + sBorderAttrs += "RightBorder:;"; + } + else + { + sBorderAttrs += "RightBorder:Color=" + + OUString::number( aRightBorder.Color ) + + sInnerSplit + + "InnerLineWidth=" + + OUString::number( static_cast<sal_Int32>(aRightBorder.InnerLineWidth) ) + + sInnerSplit + + "OuterLineWidth=" + + OUString::number( static_cast<sal_Int32>(aRightBorder.OuterLineWidth) ) + + sInnerSplit + + "LineDistance=" + + OUString::number( static_cast<sal_Int32>(aRightBorder.LineDistance) ) + + sOuterSplit; + } + return sBorderAttrs; +} +//end of cell attributes + +OUString ScAccessibleCellBase::GetAllDisplayNote() const +{ + OUString strNote; + OUString strTrackText; + if (mpDoc) + { + bool bLeftedge = false; + mpDoc->GetCellChangeTrackNote(maCellAddress,strTrackText,bLeftedge); + } + if (!strTrackText.isEmpty()) + { + ScDetectiveFunc::AppendChangTrackNoteSeparator(strTrackText); + strNote = strTrackText; + } + strNote += GetNote(); + return strNote; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleContextBase.cxx b/sc/source/ui/Accessibility/AccessibleContextBase.cxx new file mode 100644 index 0000000000..59f2f39903 --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleContextBase.cxx @@ -0,0 +1,493 @@ +/* -*- 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 <AccessibleContextBase.hxx> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/IllegalAccessibleComponentStateException.hpp> +#include <tools/gen.hxx> +#include <tools/color.hxx> +#include <toolkit/helper/convert.hxx> +#include <svl/hint.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <utility> +#include <vcl/unohelp.hxx> +#include <comphelper/accessibleeventnotifier.hxx> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +/** + The listener is an internal class to prevent reference-counting cycles and therefore memory leaks. +*/ +typedef cppu::WeakComponentImplHelper< + css::accessibility::XAccessibleEventListener + > ScAccessibleContextBaseEventListenerWeakImpl; +class ScAccessibleContextBase::ScAccessibleContextBaseEventListener : public cppu::BaseMutex, public ScAccessibleContextBaseEventListenerWeakImpl +{ +public: + ScAccessibleContextBaseEventListener(ScAccessibleContextBase& rBase) + : ScAccessibleContextBaseEventListenerWeakImpl(m_aMutex), mrBase(rBase) {} + + using WeakComponentImplHelperBase::disposing; + + ///===== XAccessibleEventListener ======================================== + + virtual void SAL_CALL disposing( const lang::EventObject& rSource ) override + { + SolarMutexGuard aGuard; + if (rSource.Source == mrBase.mxParent) + dispose(); + } + + virtual void SAL_CALL + notifyEvent( + const css::accessibility::AccessibleEventObject& /*aEvent*/ ) override {} +private: + ScAccessibleContextBase& mrBase; +}; + + +ScAccessibleContextBase::ScAccessibleContextBase( + uno::Reference<XAccessible> xParent, + const sal_Int16 aRole) + : + ScAccessibleContextBaseWeakImpl(m_aMutex), + mxParent(std::move(xParent)), + mnClientId(0), + maRole(aRole) +{ +} + +ScAccessibleContextBase::~ScAccessibleContextBase() +{ + if (!IsDefunc() && !rBHelper.bInDispose) + { + // increment refcount to prevent double call off dtor + osl_atomic_increment( &m_refCount ); + // call dispose to inform object which have a weak reference to this object + dispose(); + } +} + +void ScAccessibleContextBase::Init() +{ + // hold reference to make sure that the destructor is not called + uno::Reference< XAccessibleContext > xKeepAlive(this); + + if (mxParent.is()) + { + uno::Reference< XAccessibleEventBroadcaster > xBroadcaster (mxParent->getAccessibleContext(), uno::UNO_QUERY); + if (xBroadcaster.is()) + { + if (!mxEventListener) + mxEventListener = new ScAccessibleContextBaseEventListener(*this); + xBroadcaster->addAccessibleEventListener(mxEventListener); + } + } + msName = createAccessibleName(); + msDescription = createAccessibleDescription(); +} + +void SAL_CALL ScAccessibleContextBase::disposing() +{ + SolarMutexGuard aGuard; +// CommitDefunc(); not necessary and should not be send, because it cost a lot of time + + // hold reference to make sure that the destructor is not called + uno::Reference< XAccessibleContext > xKeepAlive(this); + + if ( mnClientId ) + { + sal_Int32 nTemClientId(mnClientId); + mnClientId = 0; + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( nTemClientId, *this ); + } + + if (mxParent.is()) + { + uno::Reference< XAccessibleEventBroadcaster > xBroadcaster (mxParent->getAccessibleContext(), uno::UNO_QUERY); + if (xBroadcaster && mxEventListener) + xBroadcaster->removeAccessibleEventListener(mxEventListener); + mxParent = nullptr; + } +} + + +//===== SfxListener ===================================================== + +void ScAccessibleContextBase::Notify( SfxBroadcaster&, const SfxHint& rHint ) +{ + if (rHint.GetId() == SfxHintId::Dying) + { + // it seems the Broadcaster is dying, since the view is dying + dispose(); + } +} + +//===== XAccessible ========================================================= + +uno::Reference< XAccessibleContext> SAL_CALL + ScAccessibleContextBase::getAccessibleContext() +{ + return this; +} + +//===== XAccessibleComponent ================================================ + +sal_Bool SAL_CALL ScAccessibleContextBase::containsPoint(const awt::Point& rPoint ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return tools::Rectangle (Point(), GetBoundingBox().GetSize()).Contains(VCLPoint(rPoint)); +} + +uno::Reference< XAccessible > SAL_CALL ScAccessibleContextBase::getAccessibleAtPoint( + const awt::Point& /* rPoint */ ) +{ + OSL_FAIL("not implemented"); + return uno::Reference<XAccessible>(); +} + +awt::Rectangle SAL_CALL ScAccessibleContextBase::getBounds( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return AWTRectangle(GetBoundingBox()); +} + +awt::Point SAL_CALL ScAccessibleContextBase::getLocation( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return AWTPoint(GetBoundingBox().TopLeft()); +} + +awt::Point SAL_CALL ScAccessibleContextBase::getLocationOnScreen( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return AWTPoint(GetBoundingBoxOnScreen().TopLeft()); +} + +awt::Size SAL_CALL ScAccessibleContextBase::getSize( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return AWTSize(GetBoundingBox().GetSize()); +} + +bool ScAccessibleContextBase::isShowing( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + bool bShowing(false); + if (mxParent.is()) + { + uno::Reference<XAccessibleComponent> xParentComponent (mxParent->getAccessibleContext(), uno::UNO_QUERY); + if (xParentComponent.is()) + { + tools::Rectangle aParentBounds(VCLRectangle(xParentComponent->getBounds())); + tools::Rectangle aBounds(VCLRectangle(getBounds())); + bShowing = aBounds.Overlaps(aParentBounds); + } + } + return bShowing; +} + +bool ScAccessibleContextBase::isVisible() +{ + return true; +} + +void SAL_CALL ScAccessibleContextBase::grabFocus( ) +{ + OSL_FAIL("not implemented"); +} + +sal_Int32 SAL_CALL ScAccessibleContextBase::getForeground( ) +{ + return sal_Int32(COL_BLACK); +} + +sal_Int32 SAL_CALL ScAccessibleContextBase::getBackground( ) +{ + return sal_Int32(COL_WHITE); +} + +//===== XAccessibleContext ================================================== + +sal_Int64 SAL_CALL ScAccessibleContextBase::getAccessibleChildCount() +{ + OSL_FAIL("should be implemented in the abrevated class"); + return 0; +} + +uno::Reference<XAccessible> SAL_CALL + ScAccessibleContextBase::getAccessibleChild(sal_Int64 /* nIndex */) +{ + OSL_FAIL("should be implemented in the abrevated class"); + return uno::Reference<XAccessible>(); +} + +uno::Reference<XAccessible> SAL_CALL + ScAccessibleContextBase::getAccessibleParent() +{ + return mxParent; +} + +sal_Int64 SAL_CALL + ScAccessibleContextBase::getAccessibleIndexInParent() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + // Use a simple but slow solution for now. Optimize later. + // Return -1 to indicate that this object's parent does not know about the + // object. + sal_Int64 nIndex(-1); + + // Iterate over all the parent's children and search for this object. + if (mxParent.is()) + { + uno::Reference<XAccessibleContext> xParentContext ( + mxParent->getAccessibleContext()); + if (xParentContext.is()) + { + sal_Int64 nChildCount = xParentContext->getAccessibleChildCount(); + for (sal_Int64 i=0; i<nChildCount; ++i) + { + uno::Reference<XAccessible> xChild (xParentContext->getAccessibleChild (i)); + if (xChild.is() && xChild.get() == this) + nIndex = i; + } + } + } + + return nIndex; +} + +sal_Int16 SAL_CALL + ScAccessibleContextBase::getAccessibleRole() +{ + return maRole; +} + +OUString SAL_CALL + ScAccessibleContextBase::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (msDescription.isEmpty()) + { + OUString sDescription(createAccessibleDescription()); + + if (msDescription != sDescription) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + aEvent.OldValue <<= msDescription; + aEvent.NewValue <<= sDescription; + + msDescription = sDescription; + + CommitChange(aEvent); + } + } + return msDescription; +} + +OUString SAL_CALL + ScAccessibleContextBase::getAccessibleName() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (msName.isEmpty()) + { + OUString sName(createAccessibleName()); + OSL_ENSURE(!sName.isEmpty(), "We should give always a name."); + + if (msName != sName) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + aEvent.OldValue <<= msName; + aEvent.NewValue <<= sName; + + msName = sName; + + CommitChange(aEvent); + } + } + return msName; +} + +uno::Reference<XAccessibleRelationSet> SAL_CALL + ScAccessibleContextBase::getAccessibleRelationSet() +{ + return new utl::AccessibleRelationSetHelper(); +} + +sal_Int64 SAL_CALL ScAccessibleContextBase::getAccessibleStateSet() +{ + return 0; +} + +lang::Locale SAL_CALL + ScAccessibleContextBase::getLocale() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (mxParent.is()) + { + uno::Reference<XAccessibleContext> xParentContext ( + mxParent->getAccessibleContext()); + if (xParentContext.is()) + return xParentContext->getLocale (); + } + + // No locale and no parent. Therefore throw exception to indicate this + // cluelessness. + throw IllegalAccessibleComponentStateException (); +} + + //===== XAccessibleEventBroadcaster ===================================== + +void SAL_CALL + ScAccessibleContextBase::addAccessibleEventListener( + const uno::Reference<XAccessibleEventListener>& xListener) +{ + if (xListener.is()) + { + SolarMutexGuard aGuard; + IsObjectValid(); + if (!IsDefunc()) + { + if (!mnClientId) + mnClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( mnClientId, xListener ); + } + } +} + +void SAL_CALL + ScAccessibleContextBase::removeAccessibleEventListener( + const uno::Reference<XAccessibleEventListener>& xListener) +{ + if (!xListener.is()) + return; + + SolarMutexGuard aGuard; + if (IsDefunc() || !mnClientId) + return; + + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( mnClientId, 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( mnClientId ); + mnClientId = 0; + } +} + +// XServiceInfo +OUString SAL_CALL ScAccessibleContextBase::getImplementationName() +{ + return "ScAccessibleContextBase"; +} + +sal_Bool SAL_CALL ScAccessibleContextBase::supportsService(const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} + +uno::Sequence< OUString> SAL_CALL + ScAccessibleContextBase::getSupportedServiceNames() +{ + return {"com.sun.star.accessibility.Accessible", + "com.sun.star.accessibility.AccessibleContext"}; +} + +//===== internal ============================================================ + +OUString + ScAccessibleContextBase::createAccessibleDescription() +{ + OSL_FAIL("should be implemented in the abrevated class"); + return OUString(); +} + +OUString ScAccessibleContextBase::createAccessibleName() +{ + OSL_FAIL("should be implemented in the abrevated class"); + return OUString(); +} + +void ScAccessibleContextBase::CommitChange(const AccessibleEventObject& rEvent) const +{ + if (mnClientId) + comphelper::AccessibleEventNotifier::addEvent( mnClientId, rEvent ); +} + +void ScAccessibleContextBase::CommitFocusGained() const +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::STATE_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(const_cast<ScAccessibleContextBase*>(this)); + aEvent.NewValue <<= AccessibleStateType::FOCUSED; + + CommitChange(aEvent); +} + +void ScAccessibleContextBase::CommitFocusLost() const +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::STATE_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(const_cast<ScAccessibleContextBase*>(this)); + aEvent.OldValue <<= AccessibleStateType::FOCUSED; + + CommitChange(aEvent); +} + +AbsoluteScreenPixelRectangle ScAccessibleContextBase::GetBoundingBoxOnScreen() const +{ + OSL_FAIL("not implemented"); + return AbsoluteScreenPixelRectangle(); +} + +tools::Rectangle ScAccessibleContextBase::GetBoundingBox() const +{ + OSL_FAIL("not implemented"); + return tools::Rectangle(); +} + +void ScAccessibleContextBase::IsObjectValid() const +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + throw lang::DisposedException(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleCsvControl.cxx b/sc/source/ui/Accessibility/AccessibleCsvControl.cxx new file mode 100644 index 0000000000..c7050d7777 --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleCsvControl.cxx @@ -0,0 +1,1405 @@ +/* -*- 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 <sal/config.h> + +#include <utility> + +#include <AccessibleCsvControl.hxx> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <unotools/accessiblerelationsethelper.hxx> +#include <comphelper/sequence.hxx> +#include <scitems.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/langitem.hxx> +#include <csvtablebox.hxx> +#include <csvcontrol.hxx> +#include <csvruler.hxx> +#include <csvgrid.hxx> +#include <AccessibleText.hxx> +#include <editsrc.hxx> +#include <scresid.hxx> +#include <strings.hrc> +#include <scmod.hxx> +#include <svtools/colorcfg.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <o3tl/string_view.hxx> + +using ::utl::AccessibleRelationSetHelper; +using ::accessibility::AccessibleStaticTextBase; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::RuntimeException; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::lang::IndexOutOfBoundsException; +using ::com::sun::star::beans::PropertyValue; +using namespace ::com::sun::star::accessibility; + +const sal_Unicode cRulerDot = '.'; +const sal_Unicode cRulerLine = '|'; + +const sal_Int32 CSV_LINE_HEADER = CSV_POS_INVALID; +const sal_uInt32 CSV_COLUMN_HEADER = CSV_COLUMN_INVALID; + +ScAccessibleCsvControl::ScAccessibleCsvControl(ScCsvControl& rControl) + : mpControl(&rControl) +{ +} + +ScAccessibleCsvControl::~ScAccessibleCsvControl() +{ + ensureDisposed(); +} + +void SAL_CALL ScAccessibleCsvControl::disposing() +{ + SolarMutexGuard aGuard; + mpControl = nullptr; + comphelper::OAccessibleComponentHelper::disposing(); +} + +// XAccessibleComponent ------------------------------------------------------- + +Reference< XAccessible > SAL_CALL ScAccessibleCsvControl::getAccessibleAtPoint( const css::awt::Point& /* rPoint */ ) +{ + ensureAlive(); + return nullptr; +} + +void SAL_CALL ScAccessibleCsvControl::grabFocus() +{ + SolarMutexGuard aGuard; + ensureAlive(); + implGetControl().GrabFocus(); +} + +// events --------------------------------------------------------------------- + +void ScAccessibleCsvControl::SendFocusEvent( bool bFocused ) +{ + Any aOldAny, aNewAny; + if (bFocused) + aNewAny <<= AccessibleStateType::FOCUSED; + else + aOldAny <<= AccessibleStateType::FOCUSED; + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aOldAny, aNewAny); +} + +void ScAccessibleCsvControl::SendCaretEvent() +{ + OSL_FAIL( "ScAccessibleCsvControl::SendCaretEvent - Illegal call" ); +} + +void ScAccessibleCsvControl::SendVisibleEvent() +{ + NotifyAccessibleEvent(AccessibleEventId::VISIBLE_DATA_CHANGED, Any(), Any()); +} + +void ScAccessibleCsvControl::SendSelectionEvent() +{ + NotifyAccessibleEvent(AccessibleEventId::SELECTION_CHANGED, Any(), Any()); +} + +void ScAccessibleCsvControl::SendTableUpdateEvent( sal_uInt32 /* nFirstColumn */, sal_uInt32 /* nLastColumn */, bool /* bAllRows */ ) +{ + OSL_FAIL( "ScAccessibleCsvControl::SendTableUpdateEvent - Illegal call" ); +} + +void ScAccessibleCsvControl::SendInsertColumnEvent( sal_uInt32 /* nFirstColumn */, sal_uInt32 /* nLastColumn */ ) +{ + OSL_FAIL( "ScAccessibleCsvControl::SendInsertColumnEvent - Illegal call" ); +} + +void ScAccessibleCsvControl::SendRemoveColumnEvent( sal_uInt32 /* nFirstColumn */, sal_uInt32 /* nLastColumn */ ) +{ + OSL_FAIL( "ScAccessibleCsvControl::SendRemoveColumnEvent - Illegal call" ); +} + +// helpers -------------------------------------------------------------------- + +css::awt::Rectangle ScAccessibleCsvControl::implGetBounds() +{ + SolarMutexGuard aGuard; + ensureAlive(); + Size aOutSize(implGetControl().GetOutputSizePixel()); + return css::awt::Rectangle(0, 0, aOutSize.Width(), aOutSize.Height()); +} + +ScCsvControl& ScAccessibleCsvControl::implGetControl() const +{ + assert(mpControl && "ScAccessibleCsvControl::implGetControl - missing control"); + return *mpControl; +} + +sal_Int64 ScAccessibleCsvControl::implCreateStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nStateSet = 0; + if (isAlive()) + { + const ScCsvControl& rCtrl = implGetControl(); + nStateSet |= AccessibleStateType::OPAQUE; + if( rCtrl.IsEnabled() ) + nStateSet |= AccessibleStateType::ENABLED; + if( rCtrl.IsReallyVisible() ) + nStateSet |= AccessibleStateType::SHOWING; + if( rCtrl.IsVisible() ) + nStateSet |= AccessibleStateType::VISIBLE; + } + else + nStateSet |= AccessibleStateType::DEFUNC; + return nStateSet; +} + +// Ruler ====================================================================== + +/** Converts a ruler cursor position to API text index. */ +static sal_Int32 lcl_GetApiPos( sal_Int32 nRulerPos ) +{ + sal_Int32 nApiPos = nRulerPos; + sal_Int32 nStart = (nRulerPos - 1) / 10; + sal_Int32 nExp = 1; + while( nStart >= nExp ) + { + nApiPos += nStart - nExp + 1; + nExp *= 10; + } + return ::std::max( nApiPos, static_cast<sal_Int32>(0) ); +} + +/** Converts an API text index to a ruler cursor position. */ +static sal_Int32 lcl_GetRulerPos( sal_Int32 nApiPos ) +{ + sal_Int32 nDiv = 10; + sal_Int32 nExp = 10; + sal_Int32 nRulerPos = 0; + sal_Int32 nApiBase = 0; + sal_Int32 nApiLimit = 10; + while( nApiPos >= nApiLimit ) + { + ++nDiv; + nRulerPos = nExp; + nExp *= 10; + nApiBase = nApiLimit; + nApiLimit = lcl_GetApiPos( nExp ); + } + sal_Int32 nRelPos = nApiPos - nApiBase; + return nRulerPos + nRelPos / nDiv * 10 + ::std::max<sal_Int32>( nRelPos % nDiv - nDiv + 10, 0 ); +} + +/** Expands the sequence's size and returns the base index of the new inserted elements. */ +static sal_Int32 lcl_ExpandSequence( Sequence< PropertyValue >& rSeq, sal_Int32 nExp ) +{ + OSL_ENSURE( nExp > 0, "lcl_ExpandSequence - invalid value" ); + rSeq.realloc( rSeq.getLength() + nExp ); + return rSeq.getLength() - nExp; +} + +/** Fills the property value rVal with the specified name and value from the item. */ +static void lcl_FillProperty( PropertyValue& rVal, const OUString& rPropName, const SfxPoolItem& rItem, sal_uInt8 nMID ) +{ + rVal.Name = rPropName; + rItem.QueryValue( rVal.Value, nMID ); +} + +/** Fills the sequence with all font attributes of rFont. */ +static void lcl_FillFontAttributes( Sequence< PropertyValue >& rSeq, const vcl::Font& rFont ) +{ + SvxFontItem aFontItem( rFont.GetFamilyType(), rFont.GetFamilyName(), rFont.GetStyleName(), rFont.GetPitch(), rFont.GetCharSet(), ATTR_FONT ); + SvxFontHeightItem aHeightItem( rFont.GetFontSize().Height(), 100, ATTR_FONT_HEIGHT ); + SvxLanguageItem aLangItem( rFont.GetLanguage(), ATTR_FONT_LANGUAGE ); + + sal_Int32 nIndex = lcl_ExpandSequence( rSeq, 7 ); + auto pSeq = rSeq.getArray(); + lcl_FillProperty( pSeq[ nIndex++ ], "CharFontName", aFontItem, MID_FONT_FAMILY_NAME ); + lcl_FillProperty( pSeq[ nIndex++ ], "CharFontFamily", aFontItem, MID_FONT_FAMILY ); + lcl_FillProperty( pSeq[ nIndex++ ], "CharFontStyleName", aFontItem, MID_FONT_STYLE_NAME ); + lcl_FillProperty( pSeq[ nIndex++ ], "CharFontCharSet", aFontItem, MID_FONT_PITCH ); + lcl_FillProperty( pSeq[ nIndex++ ], "CharFontPitch", aFontItem, MID_FONT_CHAR_SET ); + lcl_FillProperty( pSeq[ nIndex++ ], "CharHeight", aHeightItem, MID_FONTHEIGHT ); + lcl_FillProperty( pSeq[ nIndex++ ], "CharLocale", aLangItem, MID_LANG_LOCALE ); +} + +ScAccessibleCsvRuler::ScAccessibleCsvRuler(ScCsvRuler& rRuler) + : ImplInheritanceHelper(rRuler) +{ + constructStringBuffer(); +} + +ScAccessibleCsvRuler::~ScAccessibleCsvRuler() +{ + ensureDisposed(); +} + +// XAccessibleComponent ----------------------------------------------------- + +sal_Int32 SAL_CALL ScAccessibleCsvRuler::getForeground( ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + return sal_Int32(Application::GetSettings().GetStyleSettings().GetLabelTextColor()); +} + +sal_Int32 SAL_CALL ScAccessibleCsvRuler::getBackground( ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + return sal_Int32(Application::GetSettings().GetStyleSettings().GetFaceColor()); +} + +// XAccessibleContext --------------------------------------------------------- + +sal_Int64 SAL_CALL ScAccessibleCsvRuler::getAccessibleChildCount() +{ + ensureAlive(); + return 0; +} + +Reference< XAccessible > SAL_CALL ScAccessibleCsvRuler::getAccessibleChild( sal_Int64 /* nIndex */ ) +{ + ensureAlive(); + throw IndexOutOfBoundsException(); +} + +Reference< XAccessibleRelationSet > SAL_CALL ScAccessibleCsvRuler::getAccessibleRelationSet() +{ + SolarMutexGuard aGuard; + ensureAlive(); + rtl::Reference<AccessibleRelationSetHelper> pRelationSet = new AccessibleRelationSetHelper(); + + ScCsvRuler& rRuler = implGetRuler(); + ScCsvTableBox* pTableBox = rRuler.GetTableBox(); + ScCsvGrid& rGrid = pTableBox->GetGrid(); + + css::uno::Reference<css::accessibility::XAccessible> xAccObj(static_cast<ScAccessibleCsvGrid*>(rGrid.GetAccessible())); + if( xAccObj.is() ) + { + Sequence< Reference< XInterface > > aSeq{ xAccObj }; + pRelationSet->AddRelation( AccessibleRelation( AccessibleRelationType::CONTROLLER_FOR, aSeq ) ); + } + + return pRelationSet; +} + +sal_Int64 SAL_CALL ScAccessibleCsvRuler::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nStateSet = implCreateStateSet(); + if( isAlive() ) + { + nStateSet |= AccessibleStateType::FOCUSABLE; + nStateSet |= AccessibleStateType::SINGLE_LINE; + if( implGetRuler().HasFocus() ) + nStateSet |= AccessibleStateType::FOCUSED; + } + return nStateSet; +} + +// XAccessibleText ------------------------------------------------------------ + +sal_Int32 SAL_CALL ScAccessibleCsvRuler::getCaretPosition() +{ + SolarMutexGuard aGuard; + ensureAlive(); + return lcl_GetApiPos( implGetRuler().GetRulerCursorPos() ); +} + +sal_Bool SAL_CALL ScAccessibleCsvRuler::setCaretPosition( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndex( nIndex ); + ScCsvRuler& rRuler = implGetRuler(); + sal_Int32 nOldCursor = rRuler.GetRulerCursorPos(); + rRuler.Execute( CSVCMD_MOVERULERCURSOR, lcl_GetRulerPos( nIndex ) ); + return rRuler.GetRulerCursorPos() != nOldCursor; +} + +sal_Unicode SAL_CALL ScAccessibleCsvRuler::getCharacter( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndex( nIndex ); + return maBuffer[nIndex]; +} + +Sequence< PropertyValue > SAL_CALL ScAccessibleCsvRuler::getCharacterAttributes( sal_Int32 nIndex, + const css::uno::Sequence< OUString >& /* aRequestedAttributes */ ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndexWithEnd( nIndex ); + Sequence< PropertyValue > aSeq; + lcl_FillFontAttributes( aSeq, implGetRuler().GetDrawingArea()->get_ref_device().GetFont() ); + return aSeq; +} + +css::awt::Rectangle SAL_CALL ScAccessibleCsvRuler::getCharacterBounds( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndexWithEnd( nIndex ); + ScCsvRuler& rRuler = implGetRuler(); + Point aPos( rRuler.GetX( lcl_GetRulerPos( nIndex ) ) - rRuler.GetCharWidth() / 2, 0 ); + css::awt::Rectangle aRect( aPos.X(), aPos.Y(), rRuler.GetCharWidth(), rRuler.GetOutputSizePixel().Height() ); + // do not return rectangle out of window + sal_Int32 nWidth = rRuler.GetOutputSizePixel().Width(); + if( aRect.X >= nWidth ) + throw IndexOutOfBoundsException(); + if( aRect.X + aRect.Width > nWidth ) + aRect.Width = nWidth - aRect.X; + return aRect; +} + +sal_Int32 SAL_CALL ScAccessibleCsvRuler::getCharacterCount() +{ + SolarMutexGuard aGuard; + ensureAlive(); + return implGetTextLength(); +} + +sal_Int32 SAL_CALL ScAccessibleCsvRuler::getIndexAtPoint( const css::awt::Point& rPoint ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ScCsvRuler& rRuler = implGetRuler(); + // use object's coordinate system, convert to API position + return lcl_GetApiPos( ::std::clamp( rRuler.GetPosFromX( rPoint.X ), sal_Int32(0), rRuler.GetPosCount() ) ); +} + +OUString SAL_CALL ScAccessibleCsvRuler::getSelectedText() +{ + ensureAlive(); + return OUString(); +} + +sal_Int32 SAL_CALL ScAccessibleCsvRuler::getSelectionStart() +{ + ensureAlive(); + return -1; +} + +sal_Int32 SAL_CALL ScAccessibleCsvRuler::getSelectionEnd() +{ + ensureAlive(); + return -1; +} + +sal_Bool SAL_CALL ScAccessibleCsvRuler::setSelection( sal_Int32 /* nStartIndex */, sal_Int32 /* nEndIndex */ ) +{ + ensureAlive(); + return false; +} + +OUString SAL_CALL ScAccessibleCsvRuler::getText() +{ + SolarMutexGuard aGuard; + ensureAlive(); + return OUString(maBuffer.subView( 0, implGetTextLength() )); +} + +OUString SAL_CALL ScAccessibleCsvRuler::getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidRange( nStartIndex, nEndIndex ); + return OUString( maBuffer.getStr() + nStartIndex, nEndIndex - nStartIndex ); +} + +TextSegment SAL_CALL ScAccessibleCsvRuler::getTextAtIndex( sal_Int32 nIndex, sal_Int16 nTextType ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + + TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + if( (nIndex == implGetTextLength()) && (nTextType != AccessibleTextType::LINE) ) + return aResult; + + ensureValidIndex( nIndex ); + + OUStringBuffer aResultText; // will be assigned to aResult.SegmentText below + sal_Int32 nRulerPos = lcl_GetRulerPos( nIndex ); + + switch( nTextType ) + { + // single character + case AccessibleTextType::CHARACTER: + { + aResult.SegmentStart = nIndex; + aResult.SegmentEnd = nIndex; + o3tl::iterateCodePoints(maBuffer, &aResult.SegmentEnd); + for (; nIndex < aResult.SegmentEnd; nIndex++) + aResultText.append(maBuffer[nIndex]); + } + break; + + // entire number or single dot/line + case AccessibleTextType::WORD: + case AccessibleTextType::GLYPH: + aResult.SegmentStart = nIndex; + if( nRulerPos % 10 ) + aResultText.append(maBuffer[nIndex]); + else + aResultText.append( nRulerPos ); // string representation of sal_Int32!!! + break; + + // entire text + case AccessibleTextType::SENTENCE: + case AccessibleTextType::PARAGRAPH: + case AccessibleTextType::LINE: + aResult.SegmentStart = 0; + aResultText.append( maBuffer.getStr(), implGetTextLength() ); + break; + + // equal-formatted text + case AccessibleTextType::ATTRIBUTE_RUN: + { + sal_Int32 nFirstIndex = implGetFirstEqualFormatted( nIndex ); + sal_Int32 nLastIndex = implGetLastEqualFormatted( nIndex ); + aResult.SegmentStart = nFirstIndex; + aResultText.append( maBuffer.getStr() + nFirstIndex, nLastIndex - nFirstIndex + 1 ); + } + break; + + default: + throw RuntimeException(); + } + + aResult.SegmentText = aResultText.makeStringAndClear(); + aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength(); + return aResult; +} + +TextSegment SAL_CALL ScAccessibleCsvRuler::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 nTextType ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndexWithEnd( nIndex ); + + TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + sal_Int32 nRulerPos = lcl_GetRulerPos( nIndex ); + + switch( nTextType ) + { + // single character + case AccessibleTextType::CHARACTER: + if( nIndex > 0 ) + { + o3tl::iterateCodePoints(maBuffer, &nIndex, -1); + aResult = getTextAtIndex(nIndex, nTextType); + } + // else empty + break; + + // entire number or single dot/line + case AccessibleTextType::WORD: + case AccessibleTextType::GLYPH: + if( nRulerPos > 0 ) + aResult = getTextAtIndex( lcl_GetApiPos( nRulerPos - 1 ), nTextType ); + // else empty + break; + + // entire text + case AccessibleTextType::SENTENCE: + case AccessibleTextType::PARAGRAPH: + case AccessibleTextType::LINE: + // empty + break; + + // equal-formatted text + case AccessibleTextType::ATTRIBUTE_RUN: + { + sal_Int32 nFirstIndex = implGetFirstEqualFormatted( nIndex ); + if( nFirstIndex > 0 ) + aResult = getTextAtIndex( nFirstIndex - 1, nTextType ); + // else empty + } + break; + + default: + throw RuntimeException(); + } + return aResult; +} + +TextSegment SAL_CALL ScAccessibleCsvRuler::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 nTextType ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndexWithEnd( nIndex ); + + TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + sal_Int32 nRulerPos = lcl_GetRulerPos( nIndex ); + sal_Int32 nLastValid = implGetTextLength(); + + switch( nTextType ) + { + // single character + case AccessibleTextType::CHARACTER: + if( nIndex < nLastValid ) + { + o3tl::iterateCodePoints(maBuffer, &nIndex); + aResult = getTextAtIndex(nIndex, nTextType); + } + // else empty + break; + + // entire number or single dot/line + case AccessibleTextType::WORD: + case AccessibleTextType::GLYPH: + if( nRulerPos < implGetRuler().GetPosCount() ) + aResult = getTextAtIndex( lcl_GetApiPos( nRulerPos + 1 ), nTextType ); + // else empty + break; + + // entire text + case AccessibleTextType::SENTENCE: + case AccessibleTextType::PARAGRAPH: + case AccessibleTextType::LINE: + // empty + break; + + // equal-formatted text + case AccessibleTextType::ATTRIBUTE_RUN: + { + sal_Int32 nLastIndex = implGetLastEqualFormatted( nIndex ); + if( nLastIndex < nLastValid ) + aResult = getTextAtIndex( nLastIndex + 1, nTextType ); + // else empty + } + break; + + default: + throw RuntimeException(); + } + return aResult; +} + +sal_Bool SAL_CALL ScAccessibleCsvRuler::copyText( sal_Int32 /* nStartIndex */, sal_Int32 /* nEndIndex */ ) +{ + ensureAlive(); + return false; +} + +sal_Bool SAL_CALL ScAccessibleCsvRuler::scrollSubstringTo( sal_Int32 /* nStartIndex */, sal_Int32/* nEndIndex */, AccessibleScrollType /* aScrollType */ ) +{ + return false; +} + +// events --------------------------------------------------------------------- + +void ScAccessibleCsvRuler::SendCaretEvent() +{ + sal_Int32 nPos = implGetRuler().GetRulerCursorPos(); + if (nPos != CSV_POS_INVALID) + { + Any aOldValue, aNewValue; + aNewValue <<= nPos; + NotifyAccessibleEvent( AccessibleEventId::CARET_CHANGED, aOldValue, aNewValue ); + } +} + +// helpers -------------------------------------------------------------------- + +OUString SAL_CALL ScAccessibleCsvRuler::getAccessibleName() +{ + return ScResId( STR_ACC_CSVRULER_NAME ); +} + +OUString SAL_CALL ScAccessibleCsvRuler::getAccessibleDescription() +{ + return ScResId( STR_ACC_CSVRULER_DESCR ); +} + +void ScAccessibleCsvRuler::ensureValidIndex( sal_Int32 nIndex ) const +{ + if( (nIndex < 0) || (nIndex >= implGetTextLength()) ) + throw IndexOutOfBoundsException(); +} + +void ScAccessibleCsvRuler::ensureValidIndexWithEnd( sal_Int32 nIndex ) const +{ + if( (nIndex < 0) || (nIndex > implGetTextLength()) ) + throw IndexOutOfBoundsException(); +} + +void ScAccessibleCsvRuler::ensureValidRange( sal_Int32& rnStartIndex, sal_Int32& rnEndIndex ) const +{ + if( rnStartIndex > rnEndIndex ) + ::std::swap( rnStartIndex, rnEndIndex ); + if( (rnStartIndex < 0) || (rnEndIndex > implGetTextLength()) ) + throw IndexOutOfBoundsException(); +} + +ScCsvRuler& ScAccessibleCsvRuler::implGetRuler() const +{ + return static_cast< ScCsvRuler& >( implGetControl() ); +} + +void ScAccessibleCsvRuler::constructStringBuffer() +{ + SolarMutexGuard aGuard; + ensureAlive(); + // extend existing string buffer to new ruler size + sal_Int32 nRulerCount = implGetRuler().GetPosCount(); + sal_Int32 nRulerPos = lcl_GetRulerPos( maBuffer.getLength() ); + for( ; nRulerPos <= nRulerCount; ++nRulerPos ) // include last position + { + switch( nRulerPos % 10 ) + { + case 0: maBuffer.append( nRulerPos ); break; + case 5: maBuffer.append( cRulerLine ); break; + default: maBuffer.append( cRulerDot ); + } + } +} + +sal_Int32 ScAccessibleCsvRuler::implGetTextLength() const +{ + return lcl_GetApiPos( implGetRuler().GetPosCount() + 1 ); +} + +bool ScAccessibleCsvRuler::implHasSplit( sal_Int32 nApiPos ) +{ + sal_Int32 nRulerPos = lcl_GetRulerPos( nApiPos ); + return implGetRuler().HasSplit( nRulerPos ) && (nApiPos == lcl_GetApiPos( nRulerPos )); +} + +sal_Int32 ScAccessibleCsvRuler::implGetFirstEqualFormatted( sal_Int32 nApiPos ) +{ + bool bSplit = implHasSplit( nApiPos ); + while( (nApiPos > 0) && (implHasSplit( nApiPos - 1 ) == bSplit) ) + --nApiPos; + return nApiPos; +} + +sal_Int32 ScAccessibleCsvRuler::implGetLastEqualFormatted( sal_Int32 nApiPos ) +{ + bool bSplit = implHasSplit( nApiPos ); + sal_Int32 nLength = implGetTextLength(); + while( (nApiPos < nLength - 1) && (implHasSplit( nApiPos + 1 ) == bSplit) ) + ++nApiPos; + return nApiPos; +} + +css::uno::Reference<css::accessibility::XAccessible> SAL_CALL ScAccessibleCsvRuler::getAccessibleParent() +{ + return implGetControl().GetDrawingArea()->get_accessible_parent(); +} + +// Grid ======================================================================= + +/** Converts a grid columnm index to an API column index. */ +static sal_Int32 lcl_GetApiColumn( sal_uInt32 nGridColumn ) +{ + return (nGridColumn != CSV_COLUMN_HEADER) ? static_cast< sal_Int32 >( nGridColumn + 1 ) : 0; +} + +/** Converts an API columnm index to a ScCsvGrid column index. */ +static sal_uInt32 lcl_GetGridColumn( sal_Int32 nApiColumn ) +{ + return (nApiColumn > 0) ? static_cast< sal_uInt32 >( nApiColumn - 1 ) : CSV_COLUMN_HEADER; +} + +ScAccessibleCsvGrid::ScAccessibleCsvGrid(ScCsvGrid& rGrid) + : ImplInheritanceHelper(rGrid) +{ +} + +ScAccessibleCsvGrid::~ScAccessibleCsvGrid() +{ + ensureDisposed(); +} + +void ScAccessibleCsvGrid::disposing() +{ + SolarMutexGuard aGuard; + for (auto& rEntry : maAccessibleChildren) + rEntry.second->dispose(); + maAccessibleChildren.clear(); + ScAccessibleCsvControl::disposing(); +} + +// XAccessibleComponent ------------------------------------------------------- + +Reference< XAccessible > SAL_CALL ScAccessibleCsvGrid::getAccessibleAtPoint( const css::awt::Point& rPoint ) +{ + Reference< XAccessible > xRet; + if( containsPoint( rPoint ) ) + { + SolarMutexGuard aGuard; + ensureAlive(); + + const ScCsvGrid& rGrid = implGetGrid(); + // #102679#; use <= instead of <, because the offset is the size and not the point + sal_Int32 nColumn = ((rGrid.GetFirstX() <= rPoint.X) && (rPoint.X <= rGrid.GetLastX())) ? + lcl_GetApiColumn( rGrid.GetColumnFromX( rPoint.X ) ) : 0; + sal_Int32 nRow = (rPoint.Y >= rGrid.GetHdrHeight()) ? + (rGrid.GetLineFromY( rPoint.Y ) - rGrid.GetFirstVisLine() + 1) : 0; + xRet = getAccessibleCell(nRow, nColumn); + } + return xRet; +} + +sal_Int32 SAL_CALL ScAccessibleCsvGrid::getForeground( ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + return sal_Int32(Application::GetSettings().GetStyleSettings().GetButtonTextColor()); +} + +sal_Int32 SAL_CALL ScAccessibleCsvGrid::getBackground( ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + return sal_Int32(SC_MOD()->GetColorConfig().GetColorValue( ::svtools::DOCCOLOR ).nColor); +} + +// XAccessibleContext --------------------------------------------------------- + +sal_Int64 SAL_CALL ScAccessibleCsvGrid::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + ensureAlive(); + return implGetCellCount(); +} + +Reference<XAccessible> ScAccessibleCsvGrid::getAccessibleCell(sal_Int32 nRow, sal_Int32 nColumn) +{ + sal_Int64 nIndex = implGetIndex(nRow, nColumn); + + XAccessibleSet::iterator aI = maAccessibleChildren.lower_bound(nIndex); + if (aI != maAccessibleChildren.end() && !(maAccessibleChildren.key_comp()(nIndex, aI->first))) + { + // key already exists + return aI->second; + } + // key does not exist + rtl::Reference<ScAccessibleCsvCell> xNew = implCreateCellObj(nRow, nColumn); + maAccessibleChildren.insert(aI, XAccessibleSet::value_type(nIndex, xNew)); + return xNew; +} + +Reference< XAccessible > SAL_CALL ScAccessibleCsvGrid::getAccessibleChild( sal_Int64 nIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndex( nIndex ); + + return getAccessibleCell(implGetRow(nIndex), implGetColumn(nIndex)); +} + +Reference< XAccessibleRelationSet > SAL_CALL ScAccessibleCsvGrid::getAccessibleRelationSet() +{ + SolarMutexGuard aGuard; + ensureAlive(); + rtl::Reference<AccessibleRelationSetHelper> pRelationSet = new AccessibleRelationSetHelper(); + + ScCsvGrid& rGrid = implGetGrid(); + ScCsvTableBox* pTableBox = rGrid.GetTableBox(); + ScCsvRuler& rRuler = pTableBox->GetRuler(); + + if (rRuler.IsVisible()) + { + css::uno::Reference<css::accessibility::XAccessible> xAccObj(static_cast<ScAccessibleCsvGrid*>(rRuler.GetAccessible())); + if( xAccObj.is() ) + { + Sequence< Reference< XInterface > > aSeq{ xAccObj }; + pRelationSet->AddRelation( AccessibleRelation( AccessibleRelationType::CONTROLLED_BY, aSeq ) ); + } + } + + return pRelationSet; +} + +sal_Int64 SAL_CALL ScAccessibleCsvGrid::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nStateSet = implCreateStateSet(); + if( isAlive() ) + { + nStateSet |= AccessibleStateType::FOCUSABLE; + nStateSet |= AccessibleStateType::MULTI_SELECTABLE; + nStateSet |= AccessibleStateType::MANAGES_DESCENDANTS; + if( implGetGrid().HasFocus() ) + nStateSet |= AccessibleStateType::FOCUSED; + } + else + nStateSet |= AccessibleStateType::DEFUNC; + return nStateSet; +} + +// XAccessibleTable ----------------------------------------------------------- + +sal_Int32 SAL_CALL ScAccessibleCsvGrid::getAccessibleRowCount() +{ + SolarMutexGuard aGuard; + ensureAlive(); + return implGetRowCount(); +} + +sal_Int32 SAL_CALL ScAccessibleCsvGrid::getAccessibleColumnCount() +{ + SolarMutexGuard aGuard; + ensureAlive(); + return implGetColumnCount(); +} + +OUString SAL_CALL ScAccessibleCsvGrid::getAccessibleRowDescription( sal_Int32 nRow ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidPosition( nRow, 0 ); + return implGetCellText( nRow, 0 ); +} + +OUString SAL_CALL ScAccessibleCsvGrid::getAccessibleColumnDescription( sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidPosition( 0, nColumn ); + return implGetCellText( 0, nColumn ); +} + +sal_Int32 SAL_CALL ScAccessibleCsvGrid::getAccessibleRowExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + ensureAlive(); + ensureValidPosition( nRow, nColumn ); + return 1; +} + +sal_Int32 SAL_CALL ScAccessibleCsvGrid::getAccessibleColumnExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + ensureAlive(); + ensureValidPosition( nRow, nColumn ); + return 1; +} + +Reference< XAccessibleTable > SAL_CALL ScAccessibleCsvGrid::getAccessibleRowHeaders() +{ + ensureAlive(); + return nullptr; +} + +Reference< XAccessibleTable > SAL_CALL ScAccessibleCsvGrid::getAccessibleColumnHeaders() +{ + ensureAlive(); + return nullptr; +} + +Sequence< sal_Int32 > SAL_CALL ScAccessibleCsvGrid::getSelectedAccessibleRows() +{ + ensureAlive(); + return Sequence< sal_Int32 >(); +} + +Sequence< sal_Int32 > SAL_CALL ScAccessibleCsvGrid::getSelectedAccessibleColumns() +{ + SolarMutexGuard aGuard; + ensureAlive(); + + ScCsvGrid& rGrid = implGetGrid(); + Sequence< sal_Int32 > aSeq( implGetColumnCount() ); + auto pSeq = aSeq.getArray(); + + sal_Int32 nSeqIx = 0; + sal_uInt32 nColIx = rGrid.GetFirstSelected(); + for( ; nColIx != CSV_COLUMN_INVALID; ++nSeqIx, nColIx = rGrid.GetNextSelected( nColIx ) ) + pSeq[ nSeqIx ] = lcl_GetApiColumn( nColIx ); + + aSeq.realloc( nSeqIx ); + return aSeq; +} + +sal_Bool SAL_CALL ScAccessibleCsvGrid::isAccessibleRowSelected( sal_Int32 /* nRow */ ) +{ + ensureAlive(); + return false; +} + +sal_Bool SAL_CALL ScAccessibleCsvGrid::isAccessibleColumnSelected( sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndex( nColumn ); + return implIsColumnSelected( nColumn ); +} + +Reference< XAccessible > SAL_CALL ScAccessibleCsvGrid::getAccessibleCellAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidPosition( nRow, nColumn ); + return getAccessibleCell(nRow, nColumn); +} + +Reference< XAccessible > SAL_CALL ScAccessibleCsvGrid::getAccessibleCaption() +{ + ensureAlive(); + return nullptr; +} + +Reference< XAccessible > SAL_CALL ScAccessibleCsvGrid::getAccessibleSummary() +{ + ensureAlive(); + return nullptr; +} + +sal_Bool SAL_CALL ScAccessibleCsvGrid::isAccessibleSelected( sal_Int32 /* nRow */, sal_Int32 nColumn ) +{ + return isAccessibleColumnSelected( nColumn ); +} + +sal_Int64 SAL_CALL ScAccessibleCsvGrid::getAccessibleIndex( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidPosition( nRow, nColumn ); + return implGetIndex( nRow, nColumn ); +} + +sal_Int32 SAL_CALL ScAccessibleCsvGrid::getAccessibleRow( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndex( nChildIndex ); + return implGetRow( nChildIndex ); +} + +sal_Int32 SAL_CALL ScAccessibleCsvGrid::getAccessibleColumn( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndex( nChildIndex ); + return implGetColumn( nChildIndex ); +} + +// XAccessibleSelection ------------------------------------------------------- + +void SAL_CALL ScAccessibleCsvGrid::selectAccessibleChild( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndex( nChildIndex ); + sal_Int32 nColumn = implGetColumn( nChildIndex ); + if( nChildIndex == 0 ) + implGetGrid().SelectAll(); + else + implSelectColumn( nColumn, true ); +} + +sal_Bool SAL_CALL ScAccessibleCsvGrid::isAccessibleChildSelected( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndex( nChildIndex ); + sal_Int32 nColumn = implGetColumn( nChildIndex ); + return implIsColumnSelected( nColumn ); +} + +void SAL_CALL ScAccessibleCsvGrid::clearAccessibleSelection() +{ + SolarMutexGuard aGuard; + ensureAlive(); + implGetGrid().SelectAll( false ); +} + +void SAL_CALL ScAccessibleCsvGrid::selectAllAccessibleChildren() +{ + selectAccessibleChild( 0 ); +} + +sal_Int64 SAL_CALL ScAccessibleCsvGrid::getSelectedAccessibleChildCount() +{ + SolarMutexGuard aGuard; + ensureAlive(); + return static_cast<sal_Int64>(implGetRowCount()) * static_cast<sal_Int64>(implGetSelColumnCount()); +} + +Reference< XAccessible > SAL_CALL ScAccessibleCsvGrid::getSelectedAccessibleChild( sal_Int64 nSelectedChildIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + sal_Int32 nColumns = implGetSelColumnCount(); + if( nColumns == 0 ) + throw IndexOutOfBoundsException(); + + sal_Int32 nRow = nSelectedChildIndex / nColumns; + sal_Int32 nColumn = implGetSelColumn( nSelectedChildIndex % nColumns ); + return getAccessibleCellAt( nRow, nColumn ); +} + +void SAL_CALL ScAccessibleCsvGrid::deselectAccessibleChild( sal_Int64 nSelectedChildIndex ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + ensureValidIndex(nSelectedChildIndex); + sal_Int32 nColumns = implGetSelColumnCount(); + if( nColumns == 0 ) + throw IndexOutOfBoundsException(); + + sal_Int32 nColumn = implGetSelColumn( nSelectedChildIndex % nColumns ); + ensureValidPosition( nSelectedChildIndex / nColumns, nColumn ); + if( nColumn > 0 ) + implSelectColumn( nColumn, false ); +} + +// events --------------------------------------------------------------------- + +void ScAccessibleCsvGrid::SendFocusEvent( bool bFocused ) +{ + ScAccessibleCsvControl::SendFocusEvent( bFocused ); + Any aOldAny, aNewAny; + (bFocused ? aNewAny : aOldAny) <<= + getAccessibleCellAt( 0, lcl_GetApiColumn( implGetGrid().GetFocusColumn() ) ); + NotifyAccessibleEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOldAny, aNewAny); +} + +void ScAccessibleCsvGrid::SendTableUpdateEvent( sal_uInt32 nFirstColumn, sal_uInt32 nLastColumn, bool bAllRows ) +{ + if( nFirstColumn <= nLastColumn ) + { + AccessibleTableModelChange aModelChange( + AccessibleTableModelChangeType::UPDATE, 0, bAllRows ? implGetRowCount() - 1 : 0, + lcl_GetApiColumn( nFirstColumn ), lcl_GetApiColumn( nLastColumn ) ); + Any aOldAny, aNewAny; + aNewAny <<= aModelChange; + NotifyAccessibleEvent(AccessibleEventId::TABLE_MODEL_CHANGED, aOldAny, aNewAny); + } +} + +void ScAccessibleCsvGrid::SendInsertColumnEvent( sal_uInt32 nFirstColumn, sal_uInt32 nLastColumn ) +{ + if( nFirstColumn <= nLastColumn ) + { + AccessibleTableModelChange aModelChange( + AccessibleTableModelChangeType::COLUMNS_INSERTED, -1, -1, + lcl_GetApiColumn( nFirstColumn ), lcl_GetApiColumn( nLastColumn ) ); + Any aOldAny, aNewAny; + aNewAny <<= aModelChange; + NotifyAccessibleEvent(AccessibleEventId::TABLE_MODEL_CHANGED, aOldAny, aNewAny); + } +} + +void ScAccessibleCsvGrid::SendRemoveColumnEvent( sal_uInt32 nFirstColumn, sal_uInt32 nLastColumn ) +{ + if( nFirstColumn <= nLastColumn ) + { + AccessibleTableModelChange aModelChange( + AccessibleTableModelChangeType::COLUMNS_REMOVED, -1, -1, + lcl_GetApiColumn( nFirstColumn ), lcl_GetApiColumn( nLastColumn ) ); + Any aOldAny, aNewAny; + aNewAny <<= aModelChange; + NotifyAccessibleEvent(AccessibleEventId::TABLE_MODEL_CHANGED, aOldAny, aNewAny); + } +} + +// helpers -------------------------------------------------------------------- + +OUString SAL_CALL ScAccessibleCsvGrid::getAccessibleName() +{ + return ScResId( STR_ACC_CSVGRID_NAME ); +} + +OUString SAL_CALL ScAccessibleCsvGrid::getAccessibleDescription() +{ + return ScResId( STR_ACC_CSVGRID_DESCR ); +} + +void ScAccessibleCsvGrid::ensureValidIndex( sal_Int64 nIndex ) const +{ + if( (nIndex < 0) || (nIndex >= implGetCellCount()) ) + throw IndexOutOfBoundsException(); +} + +void ScAccessibleCsvGrid::ensureValidPosition( sal_Int32 nRow, sal_Int32 nColumn ) const +{ + if( (nRow < 0) || (nRow >= implGetRowCount()) || (nColumn < 0) || (nColumn >= implGetColumnCount()) ) + throw IndexOutOfBoundsException(); +} + +ScCsvGrid& ScAccessibleCsvGrid::implGetGrid() const +{ + return static_cast< ScCsvGrid& >( implGetControl() ); +} + +bool ScAccessibleCsvGrid::implIsColumnSelected( sal_Int32 nColumn ) const +{ + return (nColumn > 0) && implGetGrid().IsSelected( lcl_GetGridColumn( nColumn ) ); +} + +void ScAccessibleCsvGrid::implSelectColumn( sal_Int32 nColumn, bool bSelect ) +{ + if( nColumn > 0 ) + implGetGrid().Select( lcl_GetGridColumn( nColumn ), bSelect ); +} + +sal_Int32 ScAccessibleCsvGrid::implGetRowCount() const +{ + return static_cast< sal_Int32 >( implGetGrid().GetLastVisLine() - implGetGrid().GetFirstVisLine() + 2 ); +} + +sal_Int32 ScAccessibleCsvGrid::implGetColumnCount() const +{ + return static_cast< sal_Int32 >( implGetGrid().GetColumnCount() + 1 ); +} + +sal_Int32 ScAccessibleCsvGrid::implGetSelColumnCount() const +{ + ScCsvGrid& rGrid = implGetGrid(); + sal_Int32 nCount = 0; + for( sal_uInt32 nColIx = rGrid.GetFirstSelected(); nColIx != CSV_COLUMN_INVALID; nColIx = rGrid.GetNextSelected( nColIx ) ) + ++nCount; + return nCount; +} + +sal_Int32 ScAccessibleCsvGrid::implGetSelColumn( sal_Int32 nSelColumn ) const +{ + ScCsvGrid& rGrid = implGetGrid(); + sal_Int32 nColumn = 0; + for( sal_uInt32 nColIx = rGrid.GetFirstSelected(); nColIx != CSV_COLUMN_INVALID; nColIx = rGrid.GetNextSelected( nColIx ) ) + { + if( nColumn == nSelColumn ) + return static_cast< sal_Int32 >( nColIx + 1 ); + ++nColumn; + } + return 0; +} + +OUString ScAccessibleCsvGrid::implGetCellText( sal_Int32 nRow, sal_Int32 nColumn ) const +{ + ScCsvGrid& rGrid = implGetGrid(); + sal_Int32 nLine = nRow + rGrid.GetFirstVisLine() - 1; + OUString aCellStr; + if( (nColumn > 0) && (nRow > 0) ) + aCellStr = rGrid.GetCellText( lcl_GetGridColumn( nColumn ), nLine ); + else if( nRow > 0 ) + aCellStr = OUString::number( nLine + 1 ); + else if( nColumn > 0 ) + aCellStr = rGrid.GetColumnTypeName( lcl_GetGridColumn( nColumn ) ); + return aCellStr; +} + +rtl::Reference<ScAccessibleCsvCell> ScAccessibleCsvGrid::implCreateCellObj( sal_Int32 nRow, sal_Int32 nColumn ) +{ + return new ScAccessibleCsvCell(implGetGrid(), implGetCellText(nRow, nColumn), nRow, nColumn); +} + +css::uno::Reference<css::accessibility::XAccessible> SAL_CALL ScAccessibleCsvGrid::getAccessibleParent() +{ + return implGetControl().GetDrawingArea()->get_accessible_parent(); +} + +ScAccessibleCsvCell::ScAccessibleCsvCell( + ScCsvGrid& rGrid, + OUString aCellText, + sal_Int32 nRow, sal_Int32 nColumn ) : + ImplInheritanceHelper( rGrid ), + AccessibleStaticTextBase( SvxEditSourcePtr() ), + maCellText(std::move( aCellText )), + mnLine( nRow ? (nRow + rGrid.GetFirstVisLine() - 1) : CSV_LINE_HEADER ), + mnColumn( lcl_GetGridColumn( nColumn ) ), + mnIndex( nRow * (rGrid.GetColumnCount() + 1) + nColumn ) +{ + SetEditSource( implCreateEditSource() ); +} + +ScAccessibleCsvCell::~ScAccessibleCsvCell() +{ +} + +void SAL_CALL ScAccessibleCsvCell::disposing() +{ + SolarMutexGuard aGuard; + SetEditSource( SvxEditSourcePtr() ); + ScAccessibleCsvControl::disposing(); +} + +// XAccessibleComponent ------------------------------------------------------- + +void SAL_CALL ScAccessibleCsvCell::grabFocus() +{ + SolarMutexGuard aGuard; + ensureAlive(); + ScCsvGrid& rGrid = implGetGrid(); + rGrid.Execute( CSVCMD_MOVEGRIDCURSOR, rGrid.GetColumnPos( mnColumn ) ); +} + +sal_Int32 SAL_CALL ScAccessibleCsvCell::getForeground( ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + return sal_Int32(Application::GetSettings().GetStyleSettings().GetButtonTextColor()); +} + +sal_Int32 SAL_CALL ScAccessibleCsvCell::getBackground( ) +{ + SolarMutexGuard aGuard; + ensureAlive(); + return sal_Int32(SC_MOD()->GetColorConfig().GetColorValue( ::svtools::DOCCOLOR ).nColor); +} + +// XAccessibleContext ----------------------------------------------------- + +sal_Int64 SAL_CALL ScAccessibleCsvCell::getAccessibleChildCount() +{ + return AccessibleStaticTextBase::getAccessibleChildCount(); +} + +Reference< XAccessible > SAL_CALL ScAccessibleCsvCell::getAccessibleChild( sal_Int64 nIndex ) +{ + return AccessibleStaticTextBase::getAccessibleChild( nIndex ); +} + +sal_Int64 SAL_CALL ScAccessibleCsvCell::getAccessibleIndexInParent() +{ + SolarMutexGuard aGuard; + ensureAlive(); + return mnIndex; +} + +Reference< XAccessibleRelationSet > SAL_CALL ScAccessibleCsvCell::getAccessibleRelationSet() +{ + SolarMutexGuard aGuard; + ensureAlive(); + return new AccessibleRelationSetHelper(); +} + +sal_Int64 SAL_CALL ScAccessibleCsvCell::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nStateSet = implCreateStateSet(); + if( isAlive() ) + { + const ScCsvGrid& rGrid = implGetGrid(); + nStateSet |= AccessibleStateType::SINGLE_LINE; + if( mnColumn != CSV_COLUMN_HEADER ) + nStateSet |= AccessibleStateType::SELECTABLE; + if( rGrid.HasFocus() && (rGrid.GetFocusColumn() == mnColumn) && (mnLine == CSV_LINE_HEADER) ) + nStateSet |= AccessibleStateType::ACTIVE; + if( rGrid.IsSelected( mnColumn ) ) + nStateSet |= AccessibleStateType::SELECTED; + } + return nStateSet; +} + +// XInterface ----------------------------------------------------------------- + +IMPLEMENT_FORWARD_XINTERFACE2( ScAccessibleCsvCell, ImplInheritanceHelper, AccessibleStaticTextBase ) + +// XTypeProvider -------------------------------------------------------------- + +IMPLEMENT_FORWARD_XTYPEPROVIDER2( ScAccessibleCsvCell, ImplInheritanceHelper, AccessibleStaticTextBase ) + +// helpers -------------------------------------------------------------------- + +OUString SAL_CALL ScAccessibleCsvCell::getAccessibleName() +{ + return maCellText; +} + +OUString SAL_CALL ScAccessibleCsvCell::getAccessibleDescription() +{ + return OUString(); +} + +ScCsvGrid& ScAccessibleCsvCell::implGetGrid() const +{ + return static_cast< ScCsvGrid& >( implGetControl() ); +} + +Point ScAccessibleCsvCell::implGetRealPos() const +{ + ScCsvGrid& rGrid = implGetGrid(); + return Point( + (mnColumn == CSV_COLUMN_HEADER) ? rGrid.GetHdrX() : rGrid.GetColumnX( mnColumn ), + (mnLine == CSV_LINE_HEADER) ? 0 : rGrid.GetY( mnLine ) ); +} + +sal_uInt32 ScAccessibleCsvCell::implCalcPixelWidth(sal_uInt32 nChars) const +{ + ScCsvGrid& rGrid = implGetGrid(); + return rGrid.GetCharWidth() * nChars; +} + +Size ScAccessibleCsvCell::implGetRealSize() const +{ + ScCsvGrid& rGrid = implGetGrid(); + return Size( + (mnColumn == CSV_COLUMN_HEADER) ? rGrid.GetHdrWidth() : implCalcPixelWidth( rGrid.GetColumnWidth( mnColumn ) ), + (mnLine == CSV_LINE_HEADER) ? rGrid.GetHdrHeight() : rGrid.GetLineHeight() ); +} + +css::awt::Rectangle ScAccessibleCsvCell::implGetBounds() +{ + ScCsvGrid& rGrid = implGetGrid(); + tools::Rectangle aClipRect( Point( 0, 0 ), rGrid.GetOutputSizePixel() ); + if( mnColumn != CSV_COLUMN_HEADER ) + { + aClipRect.SetLeft( rGrid.GetFirstX() ); + aClipRect.SetRight( rGrid.GetLastX() ); + } + if( mnLine != CSV_LINE_HEADER ) + aClipRect.SetTop( rGrid.GetHdrHeight() ); + + tools::Rectangle aRect( implGetRealPos(), implGetRealSize() ); + aRect.Intersection( aClipRect ); + if( aRect.IsEmpty() ) + aRect.SetSize( Size( -1, -1 ) ); + + return css::awt::Rectangle(aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight()); +} + +::std::unique_ptr< SvxEditSource > ScAccessibleCsvCell::implCreateEditSource() +{ + ScCsvGrid& rGrid = implGetGrid(); + + ::std::unique_ptr< SvxEditSource > pEditSource( new ScAccessibilityEditSource( std::make_unique<ScAccessibleCsvTextData>(&rGrid.GetDrawingArea()->get_ref_device(), rGrid.GetEditEngine(), maCellText, implGetRealSize()) ) ); + return pEditSource; +} + +css::uno::Reference<css::accessibility::XAccessible> SAL_CALL ScAccessibleCsvCell::getAccessibleParent() +{ + ScCsvGrid& rGrid = implGetGrid(); + + ScAccessibleCsvGrid* pAcc = static_cast<ScAccessibleCsvGrid*>(rGrid.GetAccessible()); + + return pAcc; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleDocument.cxx b/sc/source/ui/Accessibility/AccessibleDocument.cxx new file mode 100644 index 0000000000..c309f1b1fe --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleDocument.cxx @@ -0,0 +1,2222 @@ +/* -*- 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 <AccessibleDocument.hxx> +#include <AccessibleSpreadsheet.hxx> +#include <tabvwsh.hxx> +#include <AccessibilityHints.hxx> +#include <document.hxx> +#include <drwlayer.hxx> +#include <DrawModelBroadcaster.hxx> +#include <drawview.hxx> +#include <gridwin.hxx> +#include <AccessibleEditObject.hxx> +#include <userdat.hxx> +#include <scresid.hxx> +#include <strings.hrc> +#include <strings.hxx> +#include <markdata.hxx> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <com/sun/star/drawing/ShapeCollection.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <o3tl/safeint.hxx> +#include <tools/gen.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdobj.hxx> +#include <svx/ShapeTypeHandler.hxx> +#include <svx/AccessibleShape.hxx> +#include <svx/AccessibleShapeTreeInfo.hxx> +#include <svx/AccessibleShapeInfo.hxx> +#include <svx/IAccessibleParent.hxx> +#include <comphelper/sequence.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/docfile.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <toolkit/helper/convert.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +#include <svx/AccessibleControlShape.hxx> +#include <svx/SvxShapeTypes.hxx> +#include <sfx2/objsh.hxx> +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <comphelper/processfactory.hxx> + +#include <algorithm> + +#include <scmod.hxx> + +#ifdef indices +#undef indices +#endif + +#ifdef extents +#undef extents +#endif + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + + //===== internal ======================================================== + +namespace { + +struct ScAccessibleShapeData +{ + ScAccessibleShapeData(css::uno::Reference< css::drawing::XShape > xShape_); + ~ScAccessibleShapeData(); + mutable rtl::Reference< ::accessibility::AccessibleShape > pAccShape; + mutable std::optional<ScAddress> xRelationCell; // if it is NULL this shape is anchored on the table + css::uno::Reference< css::drawing::XShape > xShape; + mutable bool bSelected; + bool bSelectable; + // cache these to make the sorting cheaper + std::optional<sal_Int16> mxLayerID; + std::optional<sal_Int32> mxZOrder; +}; + +} + +ScAccessibleShapeData::ScAccessibleShapeData(css::uno::Reference< css::drawing::XShape > xShape_) + : xShape(std::move(xShape_)), + bSelected(false), bSelectable(true) +{ + static constexpr OUStringLiteral gsLayerId = u"LayerID"; + static constexpr OUStringLiteral gsZOrder = u"ZOrder"; + uno::Reference< beans::XPropertySet> xProps(xShape, uno::UNO_QUERY); + if (xProps.is()) + { + uno::Any aAny = xProps->getPropertyValue(gsLayerId); + sal_Int16 nLayerID; + if (aAny >>= nLayerID) + mxLayerID = nLayerID; + sal_Int32 nZOrder; + aAny = xProps->getPropertyValue(gsZOrder); + if (aAny >>= nZOrder) + mxZOrder = nZOrder; + } +} + +ScAccessibleShapeData::~ScAccessibleShapeData() +{ + if (pAccShape.is()) + { + pAccShape->dispose(); + } +} + +namespace { + +struct ScShapeDataLess +{ + static void ConvertLayerId(sal_Int16& rLayerID) // changes the number of the LayerId so it the accessibility order + { + // note: MSVC 2017 ICE's if this is written as "switch" so use "if" + if (SC_LAYER_FRONT.get() == rLayerID) + { + rLayerID = 1; + } + else if (SC_LAYER_BACK.get() == rLayerID) + { + rLayerID = 0; + } + else if (SC_LAYER_INTERN.get() == rLayerID) + { + rLayerID = 2; + } + else if (SC_LAYER_CONTROLS.get() == rLayerID) + { + rLayerID = 3; + } + } + static bool LessThanSheet(const ScAccessibleShapeData* pData) + { + bool bResult(false); + if (pData->mxLayerID) + { + if (SdrLayerID(*pData->mxLayerID) == SC_LAYER_BACK) + bResult = true; + } + return bResult; + } + bool operator()(const ScAccessibleShapeData* pData1, const ScAccessibleShapeData* pData2) const + { + bool bResult(false); + if (pData1 && pData2) + { + if( pData1->mxLayerID && pData2->mxLayerID ) + { + sal_Int16 nLayerID1 = *pData1->mxLayerID; + sal_Int16 nLayerID2 = *pData2->mxLayerID; + if (nLayerID1 == nLayerID2) + { + if ( pData1->mxZOrder && pData2->mxZOrder ) + bResult = (*pData1->mxZOrder < *pData2->mxZOrder); + } + else + { + ConvertLayerId(nLayerID1); + ConvertLayerId(nLayerID2); + bResult = (nLayerID1 < nLayerID2); + } + } + } + else if (pData1 && !pData2) + bResult = LessThanSheet(pData1); + else if (!pData1 && pData2) + bResult = !LessThanSheet(pData2); + else + bResult = false; + return bResult; + } +}; + +} + +class ScChildrenShapes : public SfxListener, + public ::accessibility::IAccessibleParent +{ +public: + ScChildrenShapes(ScAccessibleDocument* pAccessibleDocument, ScTabViewShell* pViewShell, ScSplitPos eSplitPos); + virtual ~ScChildrenShapes() override; + + ///===== SfxListener ===================================================== + + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + + ///===== IAccessibleParent =============================================== + + virtual bool ReplaceChild ( + ::accessibility::AccessibleShape* pCurrentChild, + const css::uno::Reference< css::drawing::XShape >& _rxShape, + const tools::Long _nIndex, + const ::accessibility::AccessibleShapeTreeInfo& _rShapeTreeInfo + ) override; + + virtual ::accessibility::AccessibleControlShape* GetAccControlShapeFromModel + (css::beans::XPropertySet* pSet) override; + virtual css::uno::Reference< css::accessibility::XAccessible> + GetAccessibleCaption (const css::uno::Reference<css::drawing::XShape>& xShape) override; + ///===== Internal ======================================================== + void SetDrawBroadcaster(); + + sal_Int32 GetCount() const; + uno::Reference< XAccessible > Get(const ScAccessibleShapeData* pData) const; + uno::Reference< XAccessible > Get(sal_Int32 nIndex) const; + uno::Reference< XAccessible > GetAt(const awt::Point& rPoint) const; + + // gets the index of the shape starting on 0 (without the index of the table) + // returns the selected shape + bool IsSelected(sal_Int32 nIndex, + css::uno::Reference<css::drawing::XShape>& rShape) const; + + bool SelectionChanged(); + + void Select(sal_Int32 nIndex); + void DeselectAll(); // deselect also the table + void SelectAll(); + sal_Int32 GetSelectedCount() const; + uno::Reference< XAccessible > GetSelected(sal_Int32 nSelectedChildIndex, bool bTabSelected) const; + void Deselect(sal_Int32 nChildIndex); + + SdrPage* GetDrawPage() const; + + rtl::Reference<utl::AccessibleRelationSetHelper> GetRelationSet(const ScAddress* pAddress) const; + + void VisAreaChanged() const; +private: + typedef std::vector<ScAccessibleShapeData*> SortedShapes; + typedef std::unordered_map<css::uno::Reference< css::drawing::XShape >, ScAccessibleShapeData*> ShapesMap; + + mutable SortedShapes maZOrderedShapes; // a null pointer represents the sheet in the correct order + mutable ShapesMap maShapesMap; + mutable bool mbShapesNeedSorting; // set if maZOrderedShapes needs sorting + + mutable ::accessibility::AccessibleShapeTreeInfo maShapeTreeInfo; + mutable css::uno::Reference<css::view::XSelectionSupplier> xSelectionSupplier; + mutable sal_uInt32 mnShapesSelected; + ScTabViewShell* mpViewShell; + ScAccessibleDocument* mpAccessibleDocument; + ScSplitPos meSplitPos; + + void FillShapes(std::vector < uno::Reference < drawing::XShape > >& rShapes) const; + bool FindSelectedShapesChanges(const css::uno::Reference<css::drawing::XShapes>& xShapes) const; + + std::optional<ScAddress> GetAnchor(const uno::Reference<drawing::XShape>& xShape) const; + rtl::Reference<utl::AccessibleRelationSetHelper> GetRelationSet(const ScAccessibleShapeData* pData) const; + void SetAnchor(const uno::Reference<drawing::XShape>& xShape, ScAccessibleShapeData* pData) const; + void AddShape(const uno::Reference<drawing::XShape>& xShape, bool bCommitChange) const; + void RemoveShape(const uno::Reference<drawing::XShape>& xShape) const; + + bool FindShape(const uno::Reference<drawing::XShape>& xShape, SortedShapes::iterator& rItr) const; + + static sal_Int8 Compare(const ScAccessibleShapeData* pData1, + const ScAccessibleShapeData* pData2); +}; + +ScChildrenShapes::ScChildrenShapes(ScAccessibleDocument* pAccessibleDocument, ScTabViewShell* pViewShell, ScSplitPos eSplitPos) + : + mbShapesNeedSorting(false), + mnShapesSelected(0), + mpViewShell(pViewShell), + mpAccessibleDocument(pAccessibleDocument), + meSplitPos(eSplitPos) +{ + if (mpViewShell) + { + SfxViewFrame& rViewFrame = mpViewShell->GetViewFrame(); + xSelectionSupplier = uno::Reference<view::XSelectionSupplier>(rViewFrame.GetFrame().GetController(), uno::UNO_QUERY); + if (xSelectionSupplier.is()) + { + xSelectionSupplier->addSelectionChangeListener(mpAccessibleDocument); + uno::Reference<drawing::XShapes> xShapes(mpViewShell->getSelectedXShapes()); + if (xShapes.is()) + mnShapesSelected = xShapes->getCount(); + } + } + + maZOrderedShapes.push_back(nullptr); // add an element which represents the table + + GetCount(); // fill list with filtered shapes (no internal shapes) + + if (mnShapesSelected) + { + //set flag on every selected shape + if (!xSelectionSupplier.is()) + throw uno::RuntimeException(); + + uno::Reference<drawing::XShapes> xShapes(mpViewShell->getSelectedXShapes()); + if (xShapes.is()) + FindSelectedShapesChanges(xShapes); + } + if (!pViewShell) + return; + + ScViewData& rViewData = pViewShell->GetViewData(); + SfxBroadcaster* pDrawBC = rViewData.GetDocument().GetDrawBroadcaster(); + if (pDrawBC) + { + StartListening(*pDrawBC); + + maShapeTreeInfo.SetModelBroadcaster( new ScDrawModelBroadcaster(rViewData.GetDocument().GetDrawLayer()) ); + maShapeTreeInfo.SetSdrView(rViewData.GetScDrawView()); + maShapeTreeInfo.SetController(nullptr); + maShapeTreeInfo.SetWindow(pViewShell->GetWindowByPos(meSplitPos)); + maShapeTreeInfo.SetViewForwarder(mpAccessibleDocument); + } +} + +ScChildrenShapes::~ScChildrenShapes() +{ + for (ScAccessibleShapeData* pShapeData : maZOrderedShapes) + delete pShapeData; + if (mpViewShell) + { + SfxBroadcaster* pDrawBC = mpViewShell->GetViewData().GetDocument().GetDrawBroadcaster(); + if (pDrawBC) + EndListening(*pDrawBC); + } + if (mpAccessibleDocument && xSelectionSupplier.is()) + xSelectionSupplier->removeSelectionChangeListener(mpAccessibleDocument); +} + +void ScChildrenShapes::SetDrawBroadcaster() +{ + if (!mpViewShell) + return; + + ScViewData& rViewData = mpViewShell->GetViewData(); + SfxBroadcaster* pDrawBC = rViewData.GetDocument().GetDrawBroadcaster(); + if (pDrawBC) + { + StartListening(*pDrawBC, DuplicateHandling::Prevent); + + maShapeTreeInfo.SetModelBroadcaster( new ScDrawModelBroadcaster(rViewData.GetDocument().GetDrawLayer()) ); + maShapeTreeInfo.SetSdrView(rViewData.GetScDrawView()); + maShapeTreeInfo.SetController(nullptr); + maShapeTreeInfo.SetWindow(mpViewShell->GetWindowByPos(meSplitPos)); + maShapeTreeInfo.SetViewForwarder(mpAccessibleDocument); + } +} + +void ScChildrenShapes::Notify(SfxBroadcaster&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint) + return; + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint); + + SdrObject* pObj = const_cast<SdrObject*>(pSdrHint->GetObject()); + if (!(pObj && /*(pObj->GetLayer() != SC_LAYER_INTERN) && */(pObj->getSdrPageFromSdrObject() == GetDrawPage()) && + (pObj->getSdrPageFromSdrObject() == pObj->getParentSdrObjListFromSdrObject())) ) //only do something if the object lies direct on the page + return; + + switch (pSdrHint->GetKind()) + { + case SdrHintKind::ObjectChange : // object changed + { + uno::Reference<drawing::XShape> xShape (pObj->getUnoShape(), uno::UNO_QUERY); + if (xShape.is()) + { + mbShapesNeedSorting = true; // sort, because the z index or layer could be changed + auto it = maShapesMap.find(xShape); + if (it != maShapesMap.end()) + SetAnchor(xShape, it->second); + } + } + break; + case SdrHintKind::ObjectInserted : // new drawing object inserted + { + uno::Reference<drawing::XShape> xShape (pObj->getUnoShape(), uno::UNO_QUERY); + if (xShape.is()) + AddShape(xShape, true); + } + break; + case SdrHintKind::ObjectRemoved : // Removed drawing object from list + { + uno::Reference<drawing::XShape> xShape (pObj->getUnoShape(), uno::UNO_QUERY); + if (xShape.is()) + RemoveShape(xShape); + } + break; + default : + { + // other events are not interesting + } + break; + } +} + +bool ScChildrenShapes::ReplaceChild (::accessibility::AccessibleShape* pCurrentChild, + const css::uno::Reference< css::drawing::XShape >& _rxShape, + const tools::Long /*_nIndex*/, const ::accessibility::AccessibleShapeTreeInfo& _rShapeTreeInfo) +{ + // create the new child + rtl::Reference< ::accessibility::AccessibleShape > pReplacement(::accessibility::ShapeTypeHandler::Instance().CreateAccessibleObject ( + ::accessibility::AccessibleShapeInfo ( _rxShape, pCurrentChild->getAccessibleParent(), this ), + _rShapeTreeInfo + )); + + bool bResult(false); + if (pReplacement.is()) + { + OSL_ENSURE(pCurrentChild->GetXShape().get() == pReplacement->GetXShape().get(), "XShape changes and should be inserted sorted"); + auto it = maShapesMap.find(pCurrentChild->GetXShape()); + if (it != maShapesMap.end() && it->second->pAccShape.is()) + { + OSL_ENSURE(it->second->pAccShape == pCurrentChild, "wrong child found"); + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(mpAccessibleDocument); + aEvent.OldValue <<= uno::Reference<XAccessible>(pCurrentChild); + aEvent.IndexHint = -1; + + mpAccessibleDocument->CommitChange(aEvent); // child is gone - event + + pCurrentChild->dispose(); + } + + // Init after above possible pCurrentChild->dispose so we don't trigger the assert + // ScDrawModelBroadcaster::addShapeEventListener of duplicate listeners + pReplacement->Init(); + + if (it != maShapesMap.end()) + { + it->second->pAccShape = pReplacement; + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(mpAccessibleDocument); + aEvent.NewValue <<= uno::Reference<XAccessible>(pReplacement); + aEvent.IndexHint = -1; + + mpAccessibleDocument->CommitChange(aEvent); // child is new - event + bResult = true; + } + } + return bResult; +} + +::accessibility::AccessibleControlShape * ScChildrenShapes::GetAccControlShapeFromModel(css::beans::XPropertySet* pSet) +{ + GetCount(); // populate + for (ScAccessibleShapeData* pShape : maZOrderedShapes) + { + if (pShape) + { + rtl::Reference< ::accessibility::AccessibleShape > pAccShape(pShape->pAccShape); + if (pAccShape.is() && ::accessibility::ShapeTypeHandler::Instance().GetTypeId (pAccShape->GetXShape()) == ::accessibility::DRAWING_CONTROL) + { + ::accessibility::AccessibleControlShape *pCtlAccShape = static_cast < ::accessibility::AccessibleControlShape* >(pAccShape.get()); + if (pCtlAccShape && pCtlAccShape->GetControlModel() == pSet) + return pCtlAccShape; + } + } + } + return nullptr; +} + +css::uno::Reference < css::accessibility::XAccessible > +ScChildrenShapes::GetAccessibleCaption (const css::uno::Reference < css::drawing::XShape>& xShape) +{ + GetCount(); // populate + auto it = maShapesMap.find(xShape); + if (it == maShapesMap.end()) + return nullptr; + ScAccessibleShapeData* pShape = it->second; + css::uno::Reference< css::accessibility::XAccessible > xNewChild( pShape->pAccShape ); + if(xNewChild) + return xNewChild; + return nullptr; +} + +sal_Int32 ScChildrenShapes::GetCount() const +{ + SdrPage* pDrawPage = GetDrawPage(); + if (pDrawPage && (maZOrderedShapes.size() == 1)) // the table is always in + { + size_t nSdrObjCount = pDrawPage->GetObjCount(); + maZOrderedShapes.reserve(nSdrObjCount + 1); // the table is always in + for (const rtl::Reference<SdrObject>& pObj : *pDrawPage) + { + uno::Reference< drawing::XShape > xShape (pObj->getUnoShape(), uno::UNO_QUERY); + AddShape(xShape, false); //inserts in the correct order + } + } + return maZOrderedShapes.size(); +} + +uno::Reference< XAccessible > ScChildrenShapes::Get(const ScAccessibleShapeData* pData) const +{ + if (!pData) + return nullptr; + + if (!pData->pAccShape.is()) + { + ::accessibility::ShapeTypeHandler& rShapeHandler = ::accessibility::ShapeTypeHandler::Instance(); + ::accessibility::AccessibleShapeInfo aShapeInfo(pData->xShape, mpAccessibleDocument, const_cast<ScChildrenShapes*>(this)); + pData->pAccShape = rShapeHandler.CreateAccessibleObject( + aShapeInfo, maShapeTreeInfo); + if (pData->pAccShape.is()) + { + pData->pAccShape->Init(); + if (pData->bSelected) + pData->pAccShape->SetState(AccessibleStateType::SELECTED); + if (!pData->bSelectable) + pData->pAccShape->ResetState(AccessibleStateType::SELECTABLE); + pData->pAccShape->SetRelationSet(GetRelationSet(pData)); + } + } + return pData->pAccShape; + } + +uno::Reference< XAccessible > ScChildrenShapes::Get(sal_Int32 nIndex) const +{ + if (maZOrderedShapes.size() <= 1) + GetCount(); // fill list with filtered shapes (no internal shapes) + + if (mbShapesNeedSorting) + { + std::sort(maZOrderedShapes.begin(), maZOrderedShapes.end(), ScShapeDataLess()); + mbShapesNeedSorting = false; + } + + if (o3tl::make_unsigned(nIndex) >= maZOrderedShapes.size()) + return nullptr; + + return Get(maZOrderedShapes[nIndex]); +} + +uno::Reference< XAccessible > ScChildrenShapes::GetAt(const awt::Point& rPoint) const +{ + uno::Reference<XAccessible> xAccessible; + if(mpViewShell) + { + if (mbShapesNeedSorting) + { + std::sort(maZOrderedShapes.begin(), maZOrderedShapes.end(), ScShapeDataLess()); + mbShapesNeedSorting = false; + } + + sal_Int32 i(maZOrderedShapes.size() - 1); + bool bFound(false); + while (!bFound && i >= 0) + { + ScAccessibleShapeData* pShape = maZOrderedShapes[i]; + if (pShape) + { + if (!pShape->pAccShape.is()) + Get(pShape); + + if (pShape->pAccShape.is()) + { + Point aPoint(VCLPoint(rPoint)); + aPoint -= VCLRectangle(pShape->pAccShape->getBounds()).TopLeft(); + if (pShape->pAccShape->containsPoint(AWTPoint(aPoint))) + { + xAccessible = pShape->pAccShape.get(); + bFound = true; + } + } + else + { + OSL_FAIL("I should have an accessible shape now!"); + } + } + else + bFound = true; // this is the sheet and it lies before the rest of the shapes which are background shapes + + --i; + } + } + return xAccessible; +} + +bool ScChildrenShapes::IsSelected(sal_Int32 nIndex, + uno::Reference<drawing::XShape>& rShape) const +{ + bool bResult (false); + if (maZOrderedShapes.size() <= 1) + GetCount(); // fill list with filtered shapes (no internal shapes) + + if (!xSelectionSupplier.is()) + throw uno::RuntimeException(); + + if (mbShapesNeedSorting) + { + std::sort(maZOrderedShapes.begin(), maZOrderedShapes.end(), ScShapeDataLess()); + mbShapesNeedSorting = false; + } + + if (!maZOrderedShapes[nIndex]) + return false; + + bResult = maZOrderedShapes[nIndex]->bSelected; + rShape = maZOrderedShapes[nIndex]->xShape; + +#if OSL_DEBUG_LEVEL > 0 // test whether it is truly selected by a slower method + uno::Reference< drawing::XShape > xReturnShape; + bool bDebugResult(false); + uno::Reference<drawing::XShapes> xShapes(mpViewShell->getSelectedXShapes()); + + if (xShapes.is()) + { + sal_Int32 nCount(xShapes->getCount()); + if (nCount) + { + uno::Reference< drawing::XShape > xShape; + uno::Reference< drawing::XShape > xIndexShape = maZOrderedShapes[nIndex]->xShape; + sal_Int32 i(0); + while (!bDebugResult && (i < nCount)) + { + xShapes->getByIndex(i) >>= xShape; + if (xShape.is() && (xIndexShape.get() == xShape.get())) + { + bDebugResult = true; + xReturnShape = xShape; + } + else + ++i; + } + } + } + OSL_ENSURE((bResult == bDebugResult) && ((bResult && (rShape.get() == xReturnShape.get())) || !bResult), "found the wrong shape or result"); +#endif + + return bResult; +} + +bool ScChildrenShapes::SelectionChanged() +{ + bool bResult(false); + if (!xSelectionSupplier.is()) + throw uno::RuntimeException(); + + uno::Reference<drawing::XShapes> xShapes(mpViewShell->getSelectedXShapes()); + + bResult = FindSelectedShapesChanges(xShapes); + + return bResult; +} + +void ScChildrenShapes::Select(sal_Int32 nIndex) +{ + if (maZOrderedShapes.size() <= 1) + GetCount(); // fill list with filtered shapes (no internal shapes) + + if (!xSelectionSupplier.is()) + throw uno::RuntimeException(); + + if (mbShapesNeedSorting) + { + std::sort(maZOrderedShapes.begin(), maZOrderedShapes.end(), ScShapeDataLess()); + mbShapesNeedSorting = false; + } + + if (!maZOrderedShapes[nIndex]) + return; + + uno::Reference<drawing::XShape> xShape; + if (IsSelected(nIndex, xShape) || !maZOrderedShapes[nIndex]->bSelectable) + return; + + uno::Reference<drawing::XShapes> xShapes(mpViewShell->getSelectedXShapes()); + + if (!xShapes.is()) + xShapes = drawing::ShapeCollection::create( + comphelper::getProcessComponentContext()); + + xShapes->add(maZOrderedShapes[nIndex]->xShape); + + try + { + xSelectionSupplier->select(uno::Any(xShapes)); + maZOrderedShapes[nIndex]->bSelected = true; + if (maZOrderedShapes[nIndex]->pAccShape.is()) + maZOrderedShapes[nIndex]->pAccShape->SetState(AccessibleStateType::SELECTED); + } + catch (lang::IllegalArgumentException&) + { + } +} + +void ScChildrenShapes::DeselectAll() +{ + if (!xSelectionSupplier.is()) + throw uno::RuntimeException(); + + bool bSomethingSelected(true); + try + { + xSelectionSupplier->select(uno::Any()); //deselects all + } + catch (lang::IllegalArgumentException&) + { + OSL_FAIL("nothing selected before"); + bSomethingSelected = false; + } + + if (bSomethingSelected) + for (const ScAccessibleShapeData* pAccShapeData : maZOrderedShapes) + if (pAccShapeData) + { + pAccShapeData->bSelected = false; + if (pAccShapeData->pAccShape.is()) + pAccShapeData->pAccShape->ResetState(AccessibleStateType::SELECTED); + } +}; + + +void ScChildrenShapes::SelectAll() +{ + if (!xSelectionSupplier.is()) + throw uno::RuntimeException(); + + if (maZOrderedShapes.size() <= 1) + GetCount(); // fill list with filtered shapes (no internal shapes) + + if (maZOrderedShapes.size() <= 1) + return; + + uno::Reference<drawing::XShapes> xShapes = drawing::ShapeCollection::create( + comphelper::getProcessComponentContext()); + + try + { + for (const ScAccessibleShapeData* pAccShapeData : maZOrderedShapes) + { + if (pAccShapeData && pAccShapeData->bSelectable) + { + pAccShapeData->bSelected = true; + if (pAccShapeData->pAccShape.is()) + pAccShapeData->pAccShape->SetState(AccessibleStateType::SELECTED); + if (xShapes.is()) + xShapes->add(pAccShapeData->xShape); + } + } + xSelectionSupplier->select(uno::Any(xShapes)); + } + catch (lang::IllegalArgumentException&) + { + SelectionChanged(); // find all selected shapes and set the flags + } +} + +void ScChildrenShapes::FillShapes(std::vector < uno::Reference < drawing::XShape > >& rShapes) const +{ + uno::Reference<drawing::XShapes> xShapes(mpViewShell->getSelectedXShapes()); + if (xShapes.is()) + { + sal_uInt32 nCount(xShapes->getCount()); + for (sal_uInt32 i = 0; i < nCount; ++i) + { + uno::Reference<drawing::XShape> xShape; + xShapes->getByIndex(i) >>= xShape; + if (xShape.is()) + rShapes.push_back(xShape); + } + } +} + +sal_Int32 ScChildrenShapes::GetSelectedCount() const +{ + if (!xSelectionSupplier.is()) + throw uno::RuntimeException(); + + std::vector < uno::Reference < drawing::XShape > > aShapes; + FillShapes(aShapes); + + return aShapes.size(); +} + +uno::Reference< XAccessible > ScChildrenShapes::GetSelected(sal_Int32 nSelectedChildIndex, bool bTabSelected) const +{ + uno::Reference< XAccessible > xAccessible; + + if (maZOrderedShapes.size() <= 1) + GetCount(); // fill list with shapes + + if (!bTabSelected) + { + std::vector < uno::Reference < drawing::XShape > > aShapes; + FillShapes(aShapes); + + if (nSelectedChildIndex < 0 || o3tl::make_unsigned(nSelectedChildIndex) >= aShapes.size()) + return xAccessible; + + SortedShapes::iterator aItr; + if (FindShape(aShapes[nSelectedChildIndex], aItr)) + xAccessible = Get(*aItr); + } + else + { + if (mbShapesNeedSorting) + { + std::sort(maZOrderedShapes.begin(), maZOrderedShapes.end(), ScShapeDataLess()); + mbShapesNeedSorting = false; + } + for(const auto& rpShape : maZOrderedShapes) + { + if (!rpShape || rpShape->bSelected) + { + if (nSelectedChildIndex == 0) + { + if (rpShape) + xAccessible = rpShape->pAccShape.get(); + break; + } + else + --nSelectedChildIndex; + } + } + } + + return xAccessible; +} + +void ScChildrenShapes::Deselect(sal_Int32 nChildIndex) +{ + uno::Reference<drawing::XShape> xShape; + if (!IsSelected(nChildIndex, xShape)) // returns false if it is the sheet + return; + + if (!xShape.is()) + return; + + uno::Reference<drawing::XShapes> xShapes(mpViewShell->getSelectedXShapes()); + if (xShapes.is()) + xShapes->remove(xShape); + + try + { + xSelectionSupplier->select(uno::Any(xShapes)); + } + catch (lang::IllegalArgumentException&) + { + OSL_FAIL("something not selectable"); + } + + maZOrderedShapes[nChildIndex]->bSelected = false; + if (maZOrderedShapes[nChildIndex]->pAccShape.is()) + maZOrderedShapes[nChildIndex]->pAccShape->ResetState(AccessibleStateType::SELECTED); +} + +SdrPage* ScChildrenShapes::GetDrawPage() const +{ + SCTAB nTab(mpAccessibleDocument->getVisibleTable()); + SdrPage* pDrawPage = nullptr; + if (mpViewShell) + { + ScDocument& rDoc = mpViewShell->GetViewData().GetDocument(); + if (ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer()) + { + if (pDrawLayer->HasObjects() && (pDrawLayer->GetPageCount() > nTab)) + pDrawPage = pDrawLayer->GetPage(static_cast<sal_uInt16>(static_cast<sal_Int16>(nTab))); + } + } + return pDrawPage; +} + +rtl::Reference<utl::AccessibleRelationSetHelper> ScChildrenShapes::GetRelationSet(const ScAddress* pAddress) const +{ + rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSet; + for (const ScAccessibleShapeData* pAccShapeData : maZOrderedShapes) + { + if (pAccShapeData && + ((!pAccShapeData->xRelationCell && !pAddress) || + (pAccShapeData->xRelationCell && pAddress && (*(pAccShapeData->xRelationCell) == *pAddress)))) + { + if (!pRelationSet) + pRelationSet = new utl::AccessibleRelationSetHelper(); + + AccessibleRelation aRelation; + aRelation.TargetSet = { Get(pAccShapeData) }; + aRelation.RelationType = AccessibleRelationType::CONTROLLER_FOR; + + pRelationSet->AddRelation(aRelation); + } + } + return pRelationSet; +} + +bool ScChildrenShapes::FindSelectedShapesChanges(const uno::Reference<drawing::XShapes>& xShapes) const +{ + bool bResult(false); + SortedShapes aShapesList; + if (xShapes.is()) + { + mnShapesSelected = xShapes->getCount(); + for (sal_uInt32 i = 0; i < mnShapesSelected; ++i) + { + uno::Reference< drawing::XShape > xShape; + xShapes->getByIndex(i) >>= xShape; + if (xShape.is()) + { + ScAccessibleShapeData* pShapeData = new ScAccessibleShapeData(xShape); + aShapesList.push_back(pShapeData); + } + } + } + else + mnShapesSelected = 0; + SdrObject *pFocusedObj = nullptr; + if( mnShapesSelected == 1 && aShapesList.size() == 1) + { + pFocusedObj = SdrObject::getSdrObjectFromXShape(aShapesList[0]->xShape); + } + std::sort(aShapesList.begin(), aShapesList.end(), ScShapeDataLess()); + SortedShapes vecSelectedShapeAdd; + SortedShapes vecSelectedShapeRemove; + bool bHasSelect=false; + SortedShapes::iterator aXShapesItr(aShapesList.begin()); + SortedShapes::const_iterator aXShapesEndItr(aShapesList.end()); + SortedShapes::iterator aDataItr(maZOrderedShapes.begin()); + SortedShapes::const_iterator aDataEndItr(maZOrderedShapes.end()); + SortedShapes::const_iterator aFocusedItr = aDataEndItr; + while(aDataItr != aDataEndItr) + { + if (*aDataItr) // is it really a shape or only the sheet + { + sal_Int8 nComp(0); + if (aXShapesItr == aXShapesEndItr) + nComp = -1; // simulate that the Shape is lower, so the selection state will be removed + else + nComp = Compare(*aDataItr, *aXShapesItr); + if (nComp == 0) + { + if (!(*aDataItr)->bSelected) + { + (*aDataItr)->bSelected = true; + if ((*aDataItr)->pAccShape.is()) + { + (*aDataItr)->pAccShape->SetState(AccessibleStateType::SELECTED); + (*aDataItr)->pAccShape->SetState(AccessibleStateType::FOCUSED); + bResult = true; + vecSelectedShapeAdd.push_back(*aDataItr); + } + aFocusedItr = aDataItr; + } + else + { + bHasSelect = true; + } + ++aDataItr; + ++aXShapesItr; + } + else if (nComp < 0) + { + if ((*aDataItr)->bSelected) + { + (*aDataItr)->bSelected = false; + if ((*aDataItr)->pAccShape.is()) + { + (*aDataItr)->pAccShape->ResetState(AccessibleStateType::SELECTED); + (*aDataItr)->pAccShape->ResetState(AccessibleStateType::FOCUSED); + bResult = true; + vecSelectedShapeRemove.push_back(*aDataItr); + } + } + ++aDataItr; + } + else + { + OSL_FAIL("here is a selected shape which is not in the childlist"); + ++aXShapesItr; + --mnShapesSelected; + } + } + else + ++aDataItr; + } + bool bWinFocus=false; + if (mpViewShell) + { + ScGridWindow* pWin = static_cast<ScGridWindow*>(mpViewShell->GetWindowByPos(meSplitPos)); + if (pWin) + { + bWinFocus = pWin->HasFocus(); + } + } + const SdrMarkList* pMarkList = nullptr; + SdrObject* pMarkedObj = nullptr; + bool bIsFocuseMarked = true; + if( mpViewShell && mnShapesSelected == 1 && bWinFocus) + { + ScDrawView* pScDrawView = mpViewShell->GetViewData().GetScDrawView(); + if( pScDrawView ) + { + if( pScDrawView->GetMarkedObjectList().GetMarkCount() == 1 ) + { + pMarkList = &(pScDrawView->GetMarkedObjectList()); + pMarkedObj = pMarkList->GetMark(0)->GetMarkedSdrObj(); + uno::Reference< drawing::XShape > xMarkedXShape (pMarkedObj->getUnoShape(), uno::UNO_QUERY); + if( aFocusedItr != aDataEndItr && + (*aFocusedItr)->xShape.is() && + xMarkedXShape.is() && + (*aFocusedItr)->xShape != xMarkedXShape ) + bIsFocuseMarked = false; + } + } + } + //if ((aFocusedItr != aDataEndItr) && (*aFocusedItr)->pAccShape.is() && (mnShapesSelected == 1)) + if ( bIsFocuseMarked && (aFocusedItr != aDataEndItr) && (*aFocusedItr)->pAccShape.is() && (mnShapesSelected == 1) && bWinFocus) + { + (*aFocusedItr)->pAccShape->SetState(AccessibleStateType::FOCUSED); + } + else if( pFocusedObj && bWinFocus && pMarkList && pMarkList->GetMarkCount() == 1 && mnShapesSelected == 1 ) + { + if( pMarkedObj ) + { + uno::Reference< drawing::XShape > xMarkedXShape (pMarkedObj->getUnoShape(), uno::UNO_QUERY); + SdrObject* pUpObj = pMarkedObj->getParentSdrObjectFromSdrObject(); + + if( pMarkedObj == pFocusedObj && pUpObj ) + { + uno::Reference< drawing::XShape > xUpGroupXShape (pUpObj->getUnoShape(), uno::UNO_QUERY); + uno::Reference < XAccessible > xAccGroupShape = + const_cast<ScChildrenShapes*>(this)->GetAccessibleCaption( xUpGroupXShape ); + if( xAccGroupShape.is() ) + { + ::accessibility::AccessibleShape* pAccGroupShape = + static_cast< ::accessibility::AccessibleShape* >(xAccGroupShape.get()); + if( pAccGroupShape ) + { + sal_Int64 nCount = pAccGroupShape->getAccessibleChildCount(); + for( sal_Int64 i = 0; i < nCount; i++ ) + { + uno::Reference<XAccessible> xAccShape = pAccGroupShape->getAccessibleChild(i); + if (xAccShape.is()) + { + ::accessibility::AccessibleShape* pChildAccShape = static_cast< ::accessibility::AccessibleShape* >(xAccShape.get()); + uno::Reference< drawing::XShape > xChildShape = pChildAccShape->GetXShape(); + if (xChildShape == xMarkedXShape) + { + pChildAccShape->SetState(AccessibleStateType::FOCUSED); + } + else + { + pChildAccShape->ResetState(AccessibleStateType::FOCUSED); + } + } + } + } + } + } + } + } + if (vecSelectedShapeAdd.size() >= 10 ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN; + aEvent.Source = uno::Reference< XAccessible >(mpAccessibleDocument); + mpAccessibleDocument->CommitChange(aEvent); + } + else + { + for (const auto& rpShape : vecSelectedShapeAdd) + { + AccessibleEventObject aEvent; + if (bHasSelect) + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_ADD; + } + else + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + } + aEvent.Source = uno::Reference< XAccessible >(mpAccessibleDocument); + uno::Reference< XAccessible > xChild( rpShape->pAccShape ); + aEvent.NewValue <<= xChild; + mpAccessibleDocument->CommitChange(aEvent); + } + } + for (const auto& rpShape : vecSelectedShapeRemove) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE; + aEvent.Source = uno::Reference< XAccessible >(mpAccessibleDocument); + uno::Reference< XAccessible > xChild( rpShape->pAccShape ); + aEvent.NewValue <<= xChild; + mpAccessibleDocument->CommitChange(aEvent); + } + for(ScAccessibleShapeData*& pShapeData : aShapesList) + { + delete pShapeData; + pShapeData = nullptr; + } + return bResult; +} + +std::optional<ScAddress> ScChildrenShapes::GetAnchor(const uno::Reference<drawing::XShape>& xShape) const +{ + if (mpViewShell) + { + SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape(xShape); + uno::Reference<beans::XPropertySet> xShapeProp(xShape, uno::UNO_QUERY); + if (pSdrObj && xShapeProp.is()) + { + if (ScDrawObjData *pAnchor = ScDrawLayer::GetObjData(pSdrObj)) + return std::optional<ScAddress>(pAnchor->maStart); + } + } + + return std::optional<ScAddress>(); +} + +rtl::Reference<utl::AccessibleRelationSetHelper> ScChildrenShapes::GetRelationSet(const ScAccessibleShapeData* pData) const +{ + rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSet = new utl::AccessibleRelationSetHelper(); + + if (pData && mpAccessibleDocument) + { + uno::Reference<XAccessible> xAccessible = mpAccessibleDocument->GetAccessibleSpreadsheet(); // should be the current table + if (pData->xRelationCell && xAccessible.is()) + { + sal_Int32 nRow = pData->xRelationCell->Row(); + sal_Int32 nColumn = pData->xRelationCell->Col(); + bool bPositionUnset = nRow == -1 && nColumn == -1; + if (!bPositionUnset) + { + uno::Reference<XAccessibleTable> xAccTable(xAccessible->getAccessibleContext(), uno::UNO_QUERY); + if (xAccTable.is()) + xAccessible = xAccTable->getAccessibleCellAt(nRow, nColumn); + } + } + AccessibleRelation aRelation; + aRelation.TargetSet = { xAccessible }; + aRelation.RelationType = AccessibleRelationType::CONTROLLED_BY; + pRelationSet->AddRelation(aRelation); + } + + return pRelationSet; +} + +void ScChildrenShapes::SetAnchor(const uno::Reference<drawing::XShape>& xShape, ScAccessibleShapeData* pData) const +{ + if (pData) + { + std::optional<ScAddress> xAddress = GetAnchor(xShape); + if ((xAddress && pData->xRelationCell && (*xAddress != *(pData->xRelationCell))) || + (!xAddress && pData->xRelationCell) || (xAddress && !pData->xRelationCell)) + { + pData->xRelationCell = xAddress; + if (pData->pAccShape.is()) + pData->pAccShape->SetRelationSet(GetRelationSet(pData)); + } + } +} + +void ScChildrenShapes::AddShape(const uno::Reference<drawing::XShape>& xShape, bool bCommitChange) const +{ + assert( maShapesMap.find(xShape) == maShapesMap.end()); + + ScAccessibleShapeData* pShape = new ScAccessibleShapeData(xShape); + maZOrderedShapes.push_back(pShape); + mbShapesNeedSorting = true; + maShapesMap[xShape] = pShape; + SetAnchor(xShape, pShape); + + uno::Reference< beans::XPropertySet > xShapeProp(xShape, uno::UNO_QUERY); + if (xShapeProp.is()) + { + uno::Any aPropAny = xShapeProp->getPropertyValue("LayerID"); + sal_Int16 nLayerID = 0; + if( aPropAny >>= nLayerID ) + { + if( (SdrLayerID(nLayerID) == SC_LAYER_INTERN) || (SdrLayerID(nLayerID) == SC_LAYER_HIDDEN) ) + pShape->bSelectable = false; + else + pShape->bSelectable = true; + } + } + + if (!xSelectionSupplier.is()) + throw uno::RuntimeException(); + + uno::Reference<drawing::XShapes> xShapes(mpViewShell->getSelectedXShapes()); + uno::Reference<container::XEnumerationAccess> xEnumAcc(xShapes, uno::UNO_QUERY); + if (xEnumAcc.is()) + { + uno::Reference<container::XEnumeration> xEnum = xEnumAcc->createEnumeration(); + if (xEnum.is()) + { + uno::Reference<drawing::XShape> xSelectedShape; + bool bFound(false); + while (!bFound && xEnum->hasMoreElements()) + { + xEnum->nextElement() >>= xSelectedShape; + if (xShape.is() && (xShape.get() == xSelectedShape.get())) + { + pShape->bSelected = true; + bFound = true; + } + } + } + } + if (mpAccessibleDocument && bCommitChange) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(mpAccessibleDocument); + aEvent.NewValue <<= Get(pShape); + aEvent.IndexHint = -1; + + mpAccessibleDocument->CommitChange(aEvent); // new child - event + } +} + +void ScChildrenShapes::RemoveShape(const uno::Reference<drawing::XShape>& xShape) const +{ + if (mbShapesNeedSorting) + { + std::sort(maZOrderedShapes.begin(), maZOrderedShapes.end(), ScShapeDataLess()); + mbShapesNeedSorting = false; + } + SortedShapes::iterator aItr; + if (FindShape(xShape, aItr)) + { + if (mpAccessibleDocument) + { + uno::Reference<XAccessible> xOldAccessible (Get(*aItr)); + + delete *aItr; + maShapesMap.erase((*aItr)->xShape); + maZOrderedShapes.erase(aItr); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(mpAccessibleDocument); + aEvent.OldValue <<= xOldAccessible; + aEvent.IndexHint = -1; + + mpAccessibleDocument->CommitChange(aEvent); // child is gone - event + } + else + { + delete *aItr; + maShapesMap.erase((*aItr)->xShape); + maZOrderedShapes.erase(aItr); + } + } + else + { + OSL_FAIL("shape was not in internal list"); + } +} + +bool ScChildrenShapes::FindShape(const uno::Reference<drawing::XShape>& xShape, ScChildrenShapes::SortedShapes::iterator& rItr) const +{ + if (mbShapesNeedSorting) + { + std::sort(maZOrderedShapes.begin(), maZOrderedShapes.end(), ScShapeDataLess()); + mbShapesNeedSorting = false; + } + bool bResult(false); + ScAccessibleShapeData aShape(xShape); + rItr = std::lower_bound(maZOrderedShapes.begin(), maZOrderedShapes.end(), &aShape, ScShapeDataLess()); + if ((rItr != maZOrderedShapes.end()) && (*rItr != nullptr) && ((*rItr)->xShape.get() == xShape.get())) + bResult = true; // if the shape is found + +#if OSL_DEBUG_LEVEL > 0 // test whether it finds truly the correct shape (perhaps it is not really sorted) + SortedShapes::iterator aDebugItr = std::find_if(maZOrderedShapes.begin(), maZOrderedShapes.end(), + [&xShape](const ScAccessibleShapeData* pShape) { return pShape && (pShape->xShape.get() == xShape.get()); }); + bool bResult2 = (aDebugItr != maZOrderedShapes.end()); + OSL_ENSURE((bResult == bResult2) && ((bResult && (rItr == aDebugItr)) || !bResult), "wrong Shape found"); +#endif + return bResult; +} + +sal_Int8 ScChildrenShapes::Compare(const ScAccessibleShapeData* pData1, + const ScAccessibleShapeData* pData2) +{ + ScShapeDataLess aLess; + + bool bResult1(aLess(pData1, pData2)); + bool bResult2(aLess(pData2, pData1)); + + sal_Int8 nResult(0); + if (!bResult1 && bResult2) + nResult = 1; + else if (bResult1 && !bResult2) + nResult = -1; + + return nResult; +} + +void ScChildrenShapes::VisAreaChanged() const +{ + for (const ScAccessibleShapeData* pAccShapeData: maZOrderedShapes) + if (pAccShapeData && pAccShapeData->pAccShape.is()) + pAccShapeData->pAccShape->ViewForwarderChanged(); +} + +ScAccessibleDocument::ScAccessibleDocument( + const uno::Reference<XAccessible>& rxParent, + ScTabViewShell* pViewShell, + ScSplitPos eSplitPos) + : ScAccessibleDocumentBase(rxParent), + mpViewShell(pViewShell), + meSplitPos(eSplitPos), + mbCompleteSheetSelected(false) +{ + maVisArea = GetVisibleArea_Impl(); +} + +void ScAccessibleDocument::PreInit() +{ + if (!mpViewShell) + return; + + mpViewShell->AddAccessibilityObject(*this); + vcl::Window *pWin = mpViewShell->GetWindowByPos(meSplitPos); + if( pWin ) + { + pWin->AddChildEventListener( LINK( this, ScAccessibleDocument, 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->GetAccessible(), false ); + } + } + ScViewData& rViewData = mpViewShell->GetViewData(); + if (rViewData.HasEditView(meSplitPos)) + { + uno::Reference<XAccessible> xAcc = new ScAccessibleEditObject(this, rViewData.GetEditView(meSplitPos), + mpViewShell->GetWindowByPos(meSplitPos), GetCurrentCellName(), GetCurrentCellDescription(), + ScAccessibleEditObject::CellInEditMode); + AddChild(xAcc, false); + } +} + +void ScAccessibleDocument::Init() +{ + if(!mpChildrenShapes) + mpChildrenShapes.reset( new ScChildrenShapes(this, mpViewShell, meSplitPos) ); +} + +ScAccessibleDocument::~ScAccessibleDocument() +{ + if (!ScAccessibleContextBase::IsDefunc() && !rBHelper.bInDispose) + { + // increment refcount to prevent double call off dtor + osl_atomic_increment( &m_refCount ); + dispose(); + } +} + +void SAL_CALL ScAccessibleDocument::disposing() +{ + SolarMutexGuard aGuard; + FreeAccessibleSpreadsheet(); + if (mpViewShell) + { + vcl::Window *pWin = mpViewShell->GetWindowByPos(meSplitPos); + if( pWin ) + pWin->RemoveChildEventListener( LINK( this, ScAccessibleDocument, WindowChildEventListener )); + + mpViewShell->RemoveAccessibilityObject(*this); + mpViewShell = nullptr; + } + mpChildrenShapes.reset(); + + ScAccessibleDocumentBase::disposing(); +} + +void SAL_CALL ScAccessibleDocument::disposing( const lang::EventObject& /* Source */ ) +{ + disposing(); +} + + //===== SfxListener ===================================================== + +IMPL_LINK( ScAccessibleDocument, 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->GetAccessible(), true ); + } + } + 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->GetAccessible(), true ); + } + } + break; + default: break; + } +} + +void ScAccessibleDocument::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if (auto pFocusLostHint = dynamic_cast<const ScAccGridWinFocusLostHint*>(&rHint) ) + { + if (pFocusLostHint->GetOldGridWin() == meSplitPos) + { + if (mxTempAcc.is() && mpTempAccEdit) + mpTempAccEdit->LostFocus(); + else if (mpAccessibleSpreadsheet.is()) + mpAccessibleSpreadsheet->LostFocus(); + else + CommitFocusLost(); + } + } + else if (auto pFocusGotHint = dynamic_cast<const ScAccGridWinFocusGotHint*>(&rHint) ) + { + if (pFocusGotHint->GetNewGridWin() == meSplitPos) + { + uno::Reference<XAccessible> xAccessible; + if (mpChildrenShapes) + { + bool bTabMarked(IsTableSelected()); + xAccessible = mpChildrenShapes->GetSelected(0, bTabMarked); + } + if( xAccessible.is() ) + { + uno::Any aNewValue; + aNewValue<<=AccessibleStateType::FOCUSED; + static_cast< ::accessibility::AccessibleShape* >(xAccessible.get())-> + CommitChange(AccessibleEventId::STATE_CHANGED, + aNewValue, + uno::Any(), -1 ); + } + else + { + if (mxTempAcc.is() && mpTempAccEdit) + mpTempAccEdit->GotFocus(); + else if (mpAccessibleSpreadsheet.is()) + mpAccessibleSpreadsheet->GotFocus(); + else + CommitFocusGained(); + } + } + } + else + { + // only notify if child exist, otherwise it is not necessary + if ((rHint.GetId() == SfxHintId::ScAccTableChanged) && + mpAccessibleSpreadsheet.is()) + { + FreeAccessibleSpreadsheet(); + + // Shapes / form controls after reload not accessible, rebuild the + // mpChildrenShapes variable. + mpChildrenShapes.reset( new ScChildrenShapes( this, mpViewShell, meSplitPos ) ); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::INVALIDATE_ALL_CHILDREN; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + CommitChange(aEvent); // all children changed + + if (mpAccessibleSpreadsheet.is()) + mpAccessibleSpreadsheet->FireFirstCellFocus(); + } + else if (rHint.GetId() == SfxHintId::ScAccMakeDrawLayer) + { + if (mpChildrenShapes) + mpChildrenShapes->SetDrawBroadcaster(); + } + else if (rHint.GetId() == SfxHintId::ScAccEnterEditMode) // this event comes only on creating edit field of a cell + { + if (mpViewShell->GetViewData().GetEditActivePart() == meSplitPos) + { + ScViewData& rViewData = mpViewShell->GetViewData(); + const EditEngine* pEditEng = rViewData.GetEditView(meSplitPos)->GetEditEngine(); + if (pEditEng && pEditEng->IsUpdateLayout()) + { + mpTempAccEdit = new ScAccessibleEditObject(this, rViewData.GetEditView(meSplitPos), + mpViewShell->GetWindowByPos(meSplitPos), GetCurrentCellName(), + ScResId(STR_ACC_EDITLINE_DESCR), ScAccessibleEditObject::CellInEditMode); + uno::Reference<XAccessible> xAcc = mpTempAccEdit; + + AddChild(xAcc, true); + + if (mpAccessibleSpreadsheet.is()) + mpAccessibleSpreadsheet->LostFocus(); + else + CommitFocusLost(); + + mpTempAccEdit->GotFocus(); + } + } + } + else if (rHint.GetId() == SfxHintId::ScAccLeaveEditMode) + { + if (mxTempAcc.is()) + { + if (mpTempAccEdit) + { + mpTempAccEdit->LostFocus(); + } + RemoveChild(mxTempAcc, true); + if (mpTempAccEdit) + { + // tdf#125982 a11y use-after-free of editengine by + // ScAccessibleEditObjectTextData living past the + // the editengine of the editview passed in above + // in ScAccEnterEditMode + mpTempAccEdit->dispose(); + mpTempAccEdit = nullptr; + } + if (mpAccessibleSpreadsheet.is() && mpViewShell && mpViewShell->IsActive()) + mpAccessibleSpreadsheet->GotFocus(); + else if( mpViewShell && mpViewShell->IsActive()) + CommitFocusGained(); + } + } + else if ((rHint.GetId() == SfxHintId::ScAccVisAreaChanged) || (rHint.GetId() == SfxHintId::ScAccWindowResized)) + { + tools::Rectangle aOldVisArea(maVisArea); + maVisArea = GetVisibleArea_Impl(); + + if (maVisArea != aOldVisArea) + { + if (maVisArea.GetSize() != aOldVisArea.GetSize()) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::BOUNDRECT_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + + CommitChange(aEvent); + + if (mpAccessibleSpreadsheet.is()) + mpAccessibleSpreadsheet->BoundingBoxChanged(); + if (mpAccessibleSpreadsheet.is() && mpViewShell && mpViewShell->IsActive()) + mpAccessibleSpreadsheet->FireFirstCellFocus(); + } + else if (mpAccessibleSpreadsheet.is()) + { + mpAccessibleSpreadsheet->VisAreaChanged(); + } + if (mpChildrenShapes) + mpChildrenShapes->VisAreaChanged(); + } + } + } + + ScAccessibleDocumentBase::Notify(rBC, rHint); +} + +void SAL_CALL ScAccessibleDocument::selectionChanged( const lang::EventObject& /* aEvent */ ) +{ + bool bSelectionChanged(false); + if (mpAccessibleSpreadsheet.is()) + { + bool bOldSelected(mbCompleteSheetSelected); + mbCompleteSheetSelected = IsTableSelected(); + if (bOldSelected != mbCompleteSheetSelected) + { + mpAccessibleSpreadsheet->CompleteSelectionChanged(mbCompleteSheetSelected); + bSelectionChanged = true; + } + } + + if (mpChildrenShapes && mpChildrenShapes->SelectionChanged()) + bSelectionChanged = true; + + if (bSelectionChanged) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + + CommitChange(aEvent); + } +} + + //===== XInterface ===================================================== + +uno::Any SAL_CALL ScAccessibleDocument::queryInterface( uno::Type const & rType ) +{ + uno::Any aAny (ScAccessibleDocumentImpl::queryInterface(rType)); + return aAny.hasValue() ? aAny : ScAccessibleContextBase::queryInterface(rType); +} + +void SAL_CALL ScAccessibleDocument::acquire() + noexcept +{ + ScAccessibleContextBase::acquire(); +} + +void SAL_CALL ScAccessibleDocument::release() + noexcept +{ + ScAccessibleContextBase::release(); +} + + //===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessibleDocument::getAccessibleAtPoint( + const awt::Point& rPoint ) +{ + uno::Reference<XAccessible> xAccessible; + if (containsPoint(rPoint)) + { + SolarMutexGuard aGuard; + IsObjectValid(); + if (mpChildrenShapes) + xAccessible = mpChildrenShapes->GetAt(rPoint); + if(!xAccessible.is()) + { + if (mxTempAcc.is()) + { + uno::Reference< XAccessibleContext > xCont(mxTempAcc->getAccessibleContext()); + uno::Reference< XAccessibleComponent > xComp(xCont, uno::UNO_QUERY); + if (xComp.is()) + { + tools::Rectangle aBound(VCLRectangle(xComp->getBounds())); + if (aBound.Contains(VCLPoint(rPoint))) + xAccessible = mxTempAcc; + } + } + if (!xAccessible.is()) + xAccessible = GetAccessibleSpreadsheet(); + } + } + return xAccessible; +} + +void SAL_CALL ScAccessibleDocument::grabFocus( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (!getAccessibleParent().is()) + return; + + uno::Reference<XAccessibleComponent> xAccessibleComponent(getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY); + if (xAccessibleComponent.is()) + { + xAccessibleComponent->grabFocus(); + // grab only focus if it does not have the focus and it is not hidden + if (mpViewShell && + (mpViewShell->GetViewData().GetActivePart() != meSplitPos) && + mpViewShell->GetWindowByPos(meSplitPos)->IsVisible()) + { + mpViewShell->ActivatePart(meSplitPos); + } + } +} + + //===== XAccessibleContext ============================================== + + /// Return the number of currently visible children. +sal_Int64 SAL_CALL + ScAccessibleDocument::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + sal_Int64 nCount(1); + if (mpChildrenShapes) + nCount = mpChildrenShapes->GetCount(); // returns the count of the shapes inclusive the table + + if (mxTempAcc.is()) + ++nCount; + + return nCount; +} + + /// Return the specified child or NULL if index is invalid. +uno::Reference<XAccessible> SAL_CALL + ScAccessibleDocument::getAccessibleChild(sal_Int64 nIndex) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + uno::Reference<XAccessible> xAccessible; + if (nIndex >= 0) + { + sal_Int64 nCount(1); + if (mpChildrenShapes) + { + xAccessible = mpChildrenShapes->Get(nIndex); // returns NULL if it is the table or out of range + nCount = mpChildrenShapes->GetCount(); //there is always a table + } + if (!xAccessible.is()) + { + if (nIndex < nCount) + xAccessible = GetAccessibleSpreadsheet(); + else if (nIndex == nCount && mxTempAcc.is()) + xAccessible = mxTempAcc; + } + } + + if (!xAccessible.is()) + throw lang::IndexOutOfBoundsException(); + + return xAccessible; +} + + /// Return the set of current states. +sal_Int64 SAL_CALL + ScAccessibleDocument::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + sal_Int64 nStateSet = 0; + if (IsDefunc(nParentStates)) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + nStateSet |= AccessibleStateType::EDITABLE; + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::OPAQUE; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + } + return nStateSet; +} + +OUString SAL_CALL + ScAccessibleDocument::getAccessibleName() +{ + SolarMutexGuard g; + + OUString aName = ScResId(STR_ACC_DOC_SPREADSHEET); + ScDocument* pScDoc = GetDocument(); + if (!pScDoc) + return aName; + + ScDocShell* pObjSh = pScDoc->GetDocumentShell(); + if (!pObjSh) + return aName; + + OUString aFileName; + SfxMedium* pMed = pObjSh->GetMedium(); + if (pMed) + aFileName = pMed->GetName(); + + if (aFileName.isEmpty()) + aFileName = pObjSh->GetTitle(SFX_TITLE_APINAME); + + if (!aFileName.isEmpty()) + { + OUString aReadOnly; + if (pObjSh->IsReadOnly()) + aReadOnly = ScResId(STR_ACC_DOC_SPREADSHEET_READONLY); + + aName = aFileName + aReadOnly + " - " + aName; + } + return aName; +} + +///===== XAccessibleSelection =========================================== + +void SAL_CALL + ScAccessibleDocument::selectAccessibleChild( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (!(mpChildrenShapes && mpViewShell)) + return; + + sal_Int32 nCount(mpChildrenShapes->GetCount()); // all shapes and the table + if (mxTempAcc.is()) + ++nCount; + if (nChildIndex < 0 || nChildIndex >= nCount) + throw lang::IndexOutOfBoundsException(); + + uno::Reference < XAccessible > xAccessible = mpChildrenShapes->Get(nChildIndex); + if (xAccessible.is()) + { + bool bWasTableSelected(IsTableSelected()); + mpChildrenShapes->Select(nChildIndex); // throws no lang::IndexOutOfBoundsException if Index is too high + if (bWasTableSelected) + mpViewShell->SelectAll(); + } + else + { + mpViewShell->SelectAll(); + } +} + +sal_Bool SAL_CALL + ScAccessibleDocument::isAccessibleChildSelected( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + bool bResult(false); + + if (mpChildrenShapes) + { + sal_Int32 nCount(mpChildrenShapes->GetCount()); // all shapes and the table + if (mxTempAcc.is()) + ++nCount; + if (nChildIndex < 0 || nChildIndex >= nCount) + throw lang::IndexOutOfBoundsException(); + + uno::Reference < XAccessible > xAccessible = mpChildrenShapes->Get(nChildIndex); + if (xAccessible.is()) + { + uno::Reference<drawing::XShape> xShape; + bResult = mpChildrenShapes->IsSelected(nChildIndex, xShape); // throws no lang::IndexOutOfBoundsException if Index is too high + } + else + { + if (mxTempAcc.is() && nChildIndex == nCount) + bResult = true; + else + bResult = IsTableSelected(); + } + } + return bResult; +} + +void SAL_CALL + ScAccessibleDocument::clearAccessibleSelection( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (mpChildrenShapes) + mpChildrenShapes->DeselectAll(); //deselects all (also the table) +} + +void SAL_CALL + ScAccessibleDocument::selectAllAccessibleChildren( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (mpChildrenShapes) + mpChildrenShapes->SelectAll(); + + // select table after shapes, because while selecting shapes the table will be deselected + if (mpViewShell) + { + mpViewShell->SelectAll(); + } +} + +sal_Int64 SAL_CALL + ScAccessibleDocument::getSelectedAccessibleChildCount( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + sal_Int64 nCount(0); + + if (mpChildrenShapes) + nCount = mpChildrenShapes->GetSelectedCount(); + + if (IsTableSelected()) + ++nCount; + + if (mxTempAcc.is()) + ++nCount; + + return nCount; +} + +uno::Reference<XAccessible > SAL_CALL + ScAccessibleDocument::getSelectedAccessibleChild( sal_Int64 nSelectedChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + uno::Reference<XAccessible> xAccessible; + if (mpChildrenShapes) + { + sal_Int64 nCount(getSelectedAccessibleChildCount()); //all shapes and the table + if (nSelectedChildIndex < 0 || nSelectedChildIndex >= nCount) + throw lang::IndexOutOfBoundsException(); + + bool bTabMarked(IsTableSelected()); + + if (mpChildrenShapes) + xAccessible = mpChildrenShapes->GetSelected(nSelectedChildIndex, bTabMarked); // throws no lang::IndexOutOfBoundsException if Index is too high + if (mxTempAcc.is() && nSelectedChildIndex == nCount - 1) + xAccessible = mxTempAcc; + else if (bTabMarked) + xAccessible = GetAccessibleSpreadsheet(); + } + + OSL_ENSURE(xAccessible.is(), "here should always be an accessible object or an exception thrown"); + + return xAccessible; +} + +void SAL_CALL + ScAccessibleDocument::deselectAccessibleChild( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (!(mpChildrenShapes && mpViewShell)) + return; + + sal_Int32 nCount(mpChildrenShapes->GetCount()); // all shapes and the table + if (mxTempAcc.is()) + ++nCount; + if (nChildIndex < 0 || nChildIndex >= nCount) + throw lang::IndexOutOfBoundsException(); + + bool bTabMarked(IsTableSelected()); + + uno::Reference < XAccessible > xAccessible = mpChildrenShapes->Get(nChildIndex); + if (xAccessible.is()) + { + mpChildrenShapes->Deselect(nChildIndex); // throws no lang::IndexOutOfBoundsException if Index is too high + if (bTabMarked) + mpViewShell->SelectAll(); // select the table again + } + else if (bTabMarked) + mpViewShell->Unmark(); +} + + //===== XServiceInfo ==================================================== + +OUString SAL_CALL + ScAccessibleDocument::getImplementationName() +{ + return "ScAccessibleDocument"; +} + +uno::Sequence< OUString> SAL_CALL + ScAccessibleDocument::getSupportedServiceNames() +{ + const css::uno::Sequence<OUString> vals { "com.sun.star.AccessibleSpreadsheetDocumentView" }; + return comphelper::concatSequences(ScAccessibleContextBase::getSupportedServiceNames(), vals); +} + +//===== XTypeProvider ======================================================= + +uno::Sequence< uno::Type > SAL_CALL ScAccessibleDocument::getTypes() +{ + return comphelper::concatSequences(ScAccessibleDocumentImpl::getTypes(), ScAccessibleContextBase::getTypes()); +} + +uno::Sequence<sal_Int8> SAL_CALL + ScAccessibleDocument::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +///===== IAccessibleViewForwarder ======================================== + +tools::Rectangle ScAccessibleDocument::GetVisibleArea_Impl() const +{ + tools::Rectangle aVisRect(GetBoundingBox()); + + if (mpViewShell) + { + Point aPoint(mpViewShell->GetViewData().GetPixPos(meSplitPos)); // returns a negative Point + aPoint.setX(-aPoint.getX()); + aPoint.setY(-aPoint.getY()); + aVisRect.SetPos(aPoint); + + ScGridWindow* pWin = static_cast<ScGridWindow*>(mpViewShell->GetWindowByPos(meSplitPos)); + if (pWin) + aVisRect = pWin->PixelToLogic(aVisRect, pWin->GetDrawMapMode()); + } + + return aVisRect; +} + +tools::Rectangle ScAccessibleDocument::GetVisibleArea() const +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return maVisArea; +} + +Point ScAccessibleDocument::LogicToPixel (const Point& rPoint) const +{ + SolarMutexGuard aGuard; + IsObjectValid(); + Point aPoint; + ScGridWindow* pWin = static_cast<ScGridWindow*>(mpViewShell->GetWindowByPos(meSplitPos)); + if (pWin) + { + aPoint = pWin->LogicToPixel(rPoint, pWin->GetDrawMapMode()); + aPoint += Point(pWin->GetWindowExtentsAbsolute().TopLeft()); + } + return aPoint; +} + +Size ScAccessibleDocument::LogicToPixel (const Size& rSize) const +{ + SolarMutexGuard aGuard; + IsObjectValid(); + Size aSize; + ScGridWindow* pWin = static_cast<ScGridWindow*>(mpViewShell->GetWindowByPos(meSplitPos)); + if (pWin) + aSize = pWin->LogicToPixel(rSize, pWin->GetDrawMapMode()); + return aSize; +} + + //===== internal ======================================================== + +rtl::Reference<utl::AccessibleRelationSetHelper> ScAccessibleDocument::GetRelationSet(const ScAddress* pAddress) const +{ + rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSet; + if (mpChildrenShapes) + pRelationSet = mpChildrenShapes->GetRelationSet(pAddress); + return pRelationSet; +} + +OUString + ScAccessibleDocument::createAccessibleDescription() +{ + return STR_ACC_DOC_DESCR; +} + +OUString + ScAccessibleDocument::createAccessibleName() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + OUString sName = ScResId(STR_ACC_DOC_NAME); + sal_Int32 nNumber(sal_Int32(meSplitPos) + 1); + sName += OUString::number(nNumber); + return sName; +} + +AbsoluteScreenPixelRectangle ScAccessibleDocument::GetBoundingBoxOnScreen() const +{ + AbsoluteScreenPixelRectangle aRect; + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindowByPos(meSplitPos); + if (pWindow) + aRect = pWindow->GetWindowExtentsAbsolute(); + } + return aRect; +} + +tools::Rectangle ScAccessibleDocument::GetBoundingBox() const +{ + tools::Rectangle aRect; + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindowByPos(meSplitPos); + if (pWindow) + aRect = pWindow->GetWindowExtentsRelative(*pWindow->GetAccessibleParentWindow()); + } + return aRect; +} + +SCTAB ScAccessibleDocument::getVisibleTable() const +{ + SCTAB nVisibleTable(0); + if (mpViewShell) + nVisibleTable = mpViewShell->GetViewData().GetTabNo(); + return nVisibleTable; +} + +uno::Reference < XAccessible > + ScAccessibleDocument::GetAccessibleSpreadsheet() +{ + if (!mpAccessibleSpreadsheet.is() && mpViewShell) + { + mpAccessibleSpreadsheet = new ScAccessibleSpreadsheet(this, mpViewShell, getVisibleTable(), meSplitPos); + mpAccessibleSpreadsheet->Init(); + mbCompleteSheetSelected = IsTableSelected(); + } + return mpAccessibleSpreadsheet; +} + +void ScAccessibleDocument::FreeAccessibleSpreadsheet() +{ + if (mpAccessibleSpreadsheet.is()) + { + mpAccessibleSpreadsheet->dispose(); + mpAccessibleSpreadsheet.clear(); + } +} + +bool ScAccessibleDocument::IsTableSelected() const +{ + bool bResult (false); + if(mpViewShell) + { + SCTAB nTab(getVisibleTable()); + //#103800#; use a copy of MarkData + ScMarkData aMarkData(mpViewShell->GetViewData().GetMarkData()); + ScDocument* pDoc = GetDocument(); + if (aMarkData.IsAllMarked( ScRange( 0, 0, nTab, pDoc->MaxCol(), pDoc->MaxRow(), nTab))) + bResult = true; + } + return bResult; +} + +bool ScAccessibleDocument::IsDefunc(sal_Int64 nParentStates) +{ + return ScAccessibleContextBase::IsDefunc() || (mpViewShell == nullptr) || !getAccessibleParent().is() || + (nParentStates & AccessibleStateType::DEFUNC); +} + +void ScAccessibleDocument::AddChild(const uno::Reference<XAccessible>& xAcc, bool bFireEvent) +{ + OSL_ENSURE(!mxTempAcc.is(), "this object should be removed before"); + if (xAcc.is()) + { + mxTempAcc = xAcc; + if( bFireEvent ) + { + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference<XAccessibleContext>(this); + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= mxTempAcc; + aEvent.IndexHint = getAccessibleChildCount() - 1; + CommitChange( aEvent ); + } + } +} + +void ScAccessibleDocument::RemoveChild(const uno::Reference<XAccessible>& xAcc, bool bFireEvent) +{ + OSL_ENSURE(mxTempAcc.is(), "this object should be added before"); + if (!xAcc.is()) + return; + + OSL_ENSURE(xAcc.get() == mxTempAcc.get(), "only the same object should be removed"); + if( bFireEvent ) + { + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference<XAccessibleContext>(this); + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.OldValue <<= mxTempAcc; + aEvent.IndexHint = -1; + CommitChange( aEvent ); + } + mxTempAcc = nullptr; +} + +OUString ScAccessibleDocument::GetCurrentCellName() const +{ + OUString sName(ScResId(STR_ACC_CELL_NAME)); + if (mpViewShell) + { + // Document not needed, because only the cell address, but not the tablename is needed + OUString sAddress(mpViewShell->GetViewData().GetCurPos().Format(ScRefFlags::VALID)); + sName = sName.replaceFirst("%1", sAddress); + } + return sName; +} + +OUString ScAccessibleDocument::GetCurrentCellDescription() +{ + return OUString(); +} + +ScDocument *ScAccessibleDocument::GetDocument() const +{ + return mpViewShell ? &mpViewShell->GetViewData().GetDocument() : nullptr; +} + +ScAddress ScAccessibleDocument::GetCurCellAddress() const +{ + return mpViewShell ? mpViewShell->GetViewData().GetCurPos() : ScAddress(); +} + +uno::Any SAL_CALL ScAccessibleDocument::getExtendedAttributes() +{ + SolarMutexGuard g; + + uno::Any anyAttribute; + + sal_uInt16 sheetIndex; + OUString sSheetName; + sheetIndex = getVisibleTable(); + if(GetDocument()==nullptr) + return anyAttribute; + GetDocument()->GetName(sheetIndex,sSheetName); + OUString sValue = "page-name:" + sSheetName + + ";page-number:" + OUString::number(sheetIndex+1) + + ";total-pages:" + OUString::number(GetDocument()->GetTableCount()) + ";"; + anyAttribute <<= sValue; + return anyAttribute; +} + +sal_Int32 SAL_CALL ScAccessibleDocument::getForeground( ) +{ + return sal_Int32(COL_BLACK); +} + +sal_Int32 SAL_CALL ScAccessibleDocument::getBackground( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return sal_Int32(SC_MOD()->GetColorConfig().GetColorValue( ::svtools::DOCCOLOR ).nColor); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleDocumentBase.cxx b/sc/source/ui/Accessibility/AccessibleDocumentBase.cxx new file mode 100644 index 0000000000..78da26006f --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleDocumentBase.cxx @@ -0,0 +1,36 @@ +/* -*- 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 <AccessibleDocumentBase.hxx> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +//===== internal ======================================================== + +ScAccessibleDocumentBase::ScAccessibleDocumentBase(const uno::Reference<XAccessible>& rxParent) + : ScAccessibleContextBase(rxParent, AccessibleRole::DOCUMENT_SPREADSHEET) +{ +} + +ScAccessibleDocumentBase::~ScAccessibleDocumentBase() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleDocumentPagePreview.cxx b/sc/source/ui/Accessibility/AccessibleDocumentPagePreview.cxx new file mode 100644 index 0000000000..3161e2ba32 --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleDocumentPagePreview.cxx @@ -0,0 +1,1569 @@ +/* -*- 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 <AccessibleDocumentPagePreview.hxx> +#include <AccessiblePreviewTable.hxx> +#include <AccessiblePageHeader.hxx> +#include <AccessibilityHints.hxx> +#include <AccessibleText.hxx> +#include <document.hxx> +#include <prevwsh.hxx> +#include <prevloc.hxx> +#include <drwlayer.hxx> +#include <editsrc.hxx> +#include <scresid.hxx> +#include <strings.hrc> +#include <strings.hxx> +#include <preview.hxx> +#include <postit.hxx> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <comphelper/sequence.hxx> + +#include <tools/gen.hxx> +#include <svx/fmview.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdobj.hxx> +#include <svx/AccessibleTextHelper.hxx> +#include <svx/AccessibleShape.hxx> +#include <svx/AccessibleShapeInfo.hxx> +#include <svx/IAccessibleParent.hxx> +#include <svx/IAccessibleViewForwarder.hxx> +#include <svx/ShapeTypeHandler.hxx> +#include <toolkit/helper/convert.hxx> +#include <vcl/svapp.hxx> +#include <sfx2/docfile.hxx> + +#include <vector> +#include <algorithm> +#include <memory> +#include <utility> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +typedef std::vector< uno::Reference< XAccessible > > ScXAccVector; + +namespace { + +struct ScAccNote +{ + OUString maNoteText; + tools::Rectangle maRect; + ScAddress maNoteCell; + ::accessibility::AccessibleTextHelper* mpTextHelper; + sal_Int32 mnParaCount; + bool mbMarkNote; + + ScAccNote() + : mpTextHelper(nullptr) + , mnParaCount(0) + , mbMarkNote(false) + { + } +}; + +} + +class ScNotesChildren +{ +public: + ScNotesChildren(ScPreviewShell* pViewShell, ScAccessibleDocumentPagePreview* pAccDoc); + ~ScNotesChildren(); + void Init(const tools::Rectangle& rVisRect, sal_Int32 nOffset); + + sal_Int32 GetChildrenCount() const { return mnParagraphs;} + uno::Reference<XAccessible> GetChild(sal_Int32 nIndex) const; + uno::Reference<XAccessible> GetAt(const awt::Point& rPoint) const; + + void DataChanged(const tools::Rectangle& rVisRect); + +private: + ScPreviewShell* mpViewShell; + ScAccessibleDocumentPagePreview* mpAccDoc; + typedef std::vector<ScAccNote> ScAccNotes; + mutable ScAccNotes maNotes; + mutable ScAccNotes maMarks; + sal_Int32 mnParagraphs; + sal_Int32 mnOffset; + + ::accessibility::AccessibleTextHelper* CreateTextHelper(const OUString& rString, const tools::Rectangle& rVisRect, const ScAddress& aCellPos, bool bMarkNote, sal_Int32 nChildOffset) const; + sal_Int32 AddNotes(const ScPreviewLocationData& rData, const tools::Rectangle& rVisRect, bool bMark, ScAccNotes& rNotes); + + static sal_Int8 CompareCell(const ScAddress& aCell1, const ScAddress& aCell2); + static void CollectChildren(const ScAccNote& rNote, ScXAccVector& rVector); + sal_Int32 CheckChanges(const ScPreviewLocationData& rData, const tools::Rectangle& rVisRect, + bool bMark, ScAccNotes& rOldNotes, ScAccNotes& rNewNotes, + ScXAccVector& rOldParas, ScXAccVector& rNewParas); + + inline ScDocument* GetDocument() const; +}; + +ScNotesChildren::ScNotesChildren(ScPreviewShell* pViewShell, ScAccessibleDocumentPagePreview* pAccDoc) + : mpViewShell(pViewShell), + mpAccDoc(pAccDoc), + mnParagraphs(0), + mnOffset(0) +{ +} + +ScNotesChildren::~ScNotesChildren() +{ + for (auto & i : maNotes) + if (i.mpTextHelper) + { + delete i.mpTextHelper; + i.mpTextHelper = nullptr; + } + for (auto & i : maMarks) + if (i.mpTextHelper) + { + delete i.mpTextHelper; + i.mpTextHelper = nullptr; + } +} + +::accessibility::AccessibleTextHelper* ScNotesChildren::CreateTextHelper(const OUString& rString, const tools::Rectangle& rVisRect, const ScAddress& aCellPos, bool bMarkNote, sal_Int32 nChildOffset) const +{ + ::accessibility::AccessibleTextHelper* pTextHelper = new ::accessibility::AccessibleTextHelper(std::make_unique<ScAccessibilityEditSource>(std::make_unique<ScAccessibleNoteTextData>(mpViewShell, rString, aCellPos, bMarkNote))); + pTextHelper->SetEventSource(mpAccDoc); + pTextHelper->SetStartIndex(nChildOffset); + pTextHelper->SetOffset(rVisRect.TopLeft()); + + return pTextHelper; +} + +sal_Int32 ScNotesChildren::AddNotes(const ScPreviewLocationData& rData, const tools::Rectangle& rVisRect, bool bMark, ScAccNotes& rNotes) +{ + sal_Int32 nCount = rData.GetNoteCountInRange(rVisRect, bMark); + + rNotes.reserve(nCount); + + sal_Int32 nParagraphs(0); + ScDocument* pDoc = GetDocument(); + if (pDoc) + { + ScAccNote aNote; + aNote.mbMarkNote = bMark; + if (bMark) + aNote.mnParaCount = 1; + for (sal_Int32 nIndex = 0; nIndex < nCount; ++nIndex) + { + if (rData.GetNoteInRange(rVisRect, nIndex, bMark, aNote.maNoteCell, aNote.maRect)) + { + if (bMark) + { + // Document not needed, because only the cell address, but not the tablename is needed + aNote.maNoteText = aNote.maNoteCell.Format(ScRefFlags::VALID); + } + else + { + if( ScPostIt* pNote = pDoc->GetNote( aNote.maNoteCell ) ) + aNote.maNoteText = pNote->GetText(); + aNote.mpTextHelper = CreateTextHelper(aNote.maNoteText, aNote.maRect, aNote.maNoteCell, aNote.mbMarkNote, nParagraphs + mnOffset); + if (aNote.mpTextHelper) + aNote.mnParaCount = aNote.mpTextHelper->GetChildCount(); + } + nParagraphs += aNote.mnParaCount; + rNotes.push_back(aNote); + } + } + } + return nParagraphs; +} + +void ScNotesChildren::Init(const tools::Rectangle& rVisRect, sal_Int32 nOffset) +{ + if (mpViewShell && !mnParagraphs) + { + mnOffset = nOffset; + const ScPreviewLocationData& rData = mpViewShell->GetLocationData(); + + mnParagraphs = AddNotes(rData, rVisRect, false, maMarks); + mnParagraphs += AddNotes(rData, rVisRect, true, maNotes); + } +} + +namespace { + +struct ScParaFound +{ + sal_Int32 mnIndex; + explicit ScParaFound(sal_Int32 nIndex) : mnIndex(nIndex) {} + bool operator() (const ScAccNote& rNote) + { + bool bResult(false); + if (rNote.mnParaCount > mnIndex) + bResult = true; + else + mnIndex -= rNote.mnParaCount; + return bResult; + } +}; + +} + +uno::Reference<XAccessible> ScNotesChildren::GetChild(sal_Int32 nIndex) const +{ + uno::Reference<XAccessible> xAccessible; + + if (nIndex < mnParagraphs) + { + if (nIndex < static_cast<sal_Int32>(maMarks.size())) + { + ScAccNotes::iterator aEndItr = maMarks.end(); + ScParaFound aParaFound(nIndex); + ScAccNotes::iterator aItr = std::find_if(maMarks.begin(), aEndItr, aParaFound); + if (aItr != aEndItr) + { + OSL_ENSURE((aItr->maNoteCell == maMarks[nIndex].maNoteCell) && (aItr->mbMarkNote == maMarks[nIndex].mbMarkNote), "wrong note found"); + if (!aItr->mpTextHelper) + aItr->mpTextHelper = CreateTextHelper(maMarks[nIndex].maNoteText, maMarks[nIndex].maRect, maMarks[nIndex].maNoteCell, maMarks[nIndex].mbMarkNote, nIndex + mnOffset); // the marks are the first and every mark has only one paragraph + xAccessible = aItr->mpTextHelper->GetChild(aParaFound.mnIndex + aItr->mpTextHelper->GetStartIndex()); + } + else + { + OSL_FAIL("wrong note found"); + } + } + else + { + nIndex -= maMarks.size(); + ScAccNotes::iterator aEndItr = maNotes.end(); + ScParaFound aParaFound(nIndex); + ScAccNotes::iterator aItr = std::find_if(maNotes.begin(), aEndItr, aParaFound); + if (aEndItr != aItr) + { + if (!aItr->mpTextHelper) + aItr->mpTextHelper = CreateTextHelper(aItr->maNoteText, aItr->maRect, aItr->maNoteCell, aItr->mbMarkNote, (nIndex - aParaFound.mnIndex) + mnOffset + maMarks.size()); + xAccessible = aItr->mpTextHelper->GetChild(aParaFound.mnIndex + aItr->mpTextHelper->GetStartIndex()); + } + } + } + + return xAccessible; +} + +namespace { + +struct ScPointFound +{ + tools::Rectangle maPoint; + sal_Int32 mnParagraphs; + explicit ScPointFound(const Point& rPoint) : maPoint(rPoint, Size(0, 0)), mnParagraphs(0) {} + bool operator() (const ScAccNote& rNote) + { + bool bResult(false); + if (maPoint.Contains(rNote.maRect)) + bResult = true; + else + mnParagraphs += rNote.mnParaCount; + return bResult; + } +}; + +} + +uno::Reference<XAccessible> ScNotesChildren::GetAt(const awt::Point& rPoint) const +{ + uno::Reference<XAccessible> xAccessible; + + ScPointFound aPointFound(Point(rPoint.X, rPoint.Y)); + + ScAccNotes::iterator aEndItr = maMarks.end(); + ScAccNotes::iterator aItr = std::find_if(maMarks.begin(), aEndItr, aPointFound); + if (aEndItr == aItr) + { + aEndItr = maNotes.end(); + aItr = std::find_if(maNotes.begin(), aEndItr, aPointFound); + } + if (aEndItr != aItr) + { + if (!aItr->mpTextHelper) + aItr->mpTextHelper = CreateTextHelper(aItr->maNoteText, aItr->maRect, aItr->maNoteCell, aItr->mbMarkNote, aPointFound.mnParagraphs + mnOffset); + xAccessible = aItr->mpTextHelper->GetAt(rPoint); + } + + return xAccessible; +} + +sal_Int8 ScNotesChildren::CompareCell(const ScAddress& aCell1, const ScAddress& aCell2) +{ + OSL_ENSURE(aCell1.Tab() == aCell2.Tab(), "the notes should be on the same table"); + sal_Int8 nResult(0); + if (aCell1 != aCell2) + { + if (aCell1.Row() == aCell2.Row()) + nResult = (aCell1.Col() < aCell2.Col()) ? -1 : 1; + else + nResult = (aCell1.Row() < aCell2.Row()) ? -1 : 1; + } + return nResult; +} + +void ScNotesChildren::CollectChildren(const ScAccNote& rNote, ScXAccVector& rVector) +{ + if (rNote.mpTextHelper) + for (sal_Int32 i = 0; i < rNote.mnParaCount; ++i) + rVector.push_back(rNote.mpTextHelper->GetChild(i + rNote.mpTextHelper->GetStartIndex())); +} + +sal_Int32 ScNotesChildren::CheckChanges(const ScPreviewLocationData& rData, + const tools::Rectangle& rVisRect, bool bMark, ScAccNotes& rOldNotes, + ScAccNotes& rNewNotes, ScXAccVector& rOldParas, ScXAccVector& rNewParas) +{ + sal_Int32 nCount = rData.GetNoteCountInRange(rVisRect, bMark); + + rNewNotes.reserve(nCount); + + sal_Int32 nParagraphs(0); + ScDocument* pDoc = GetDocument(); + if (pDoc) + { + ScAccNote aNote; + aNote.mbMarkNote = bMark; + if (bMark) + aNote.mnParaCount = 1; + ScAccNotes::iterator aItr = rOldNotes.begin(); + ScAccNotes::iterator aEndItr = rOldNotes.end(); + bool bAddNote(false); + for (sal_Int32 nIndex = 0; nIndex < nCount; ++nIndex) + { + if (rData.GetNoteInRange(rVisRect, nIndex, bMark, aNote.maNoteCell, aNote.maRect)) + { + if (bMark) + { + // Document not needed, because only the cell address, but not the tablename is needed + aNote.maNoteText = aNote.maNoteCell.Format(ScRefFlags::VALID); + } + else + { + if( ScPostIt* pNote = pDoc->GetNote( aNote.maNoteCell ) ) + aNote.maNoteText = pNote->GetText(); + } + + sal_Int8 nCompare(-1); // if there are no more old children it is always a new one + if (aItr != aEndItr) + nCompare = CompareCell(aNote.maNoteCell, aItr->maNoteCell); + if (nCompare == 0) + { + if (aNote.maNoteText == aItr->maNoteText) + { + aNote.mpTextHelper = aItr->mpTextHelper; + if (aNote.maRect != aItr->maRect) // set new VisArea + { + aNote.mpTextHelper->SetOffset(aNote.maRect.TopLeft()); + aNote.mpTextHelper->UpdateChildren(); + //OSL_ENSURE(aItr->maRect.GetSize() == aNote.maRect.GetSize(), "size should be the same, because the text is not changed"); + // could be changed, because only a part of the note is visible + } + } + else + { + aNote.mpTextHelper = CreateTextHelper(aNote.maNoteText, aNote.maRect, aNote.maNoteCell, aNote.mbMarkNote, nParagraphs + mnOffset); + if (aNote.mpTextHelper) + aNote.mnParaCount = aNote.mpTextHelper->GetChildCount(); + // collect removed children + CollectChildren(*aItr, rOldParas); + delete aItr->mpTextHelper; + aItr->mpTextHelper = nullptr;; + // collect new children + CollectChildren(aNote, rNewParas); + } + bAddNote = true; + // not necessary, because this branch should not be reached if it is the end + //if (aItr != aEndItr) + ++aItr; + } + else if (nCompare < 0) + { + aNote.mpTextHelper = CreateTextHelper(aNote.maNoteText, aNote.maRect, aNote.maNoteCell, aNote.mbMarkNote, nParagraphs + mnOffset); + if (aNote.mpTextHelper) + aNote.mnParaCount = aNote.mpTextHelper->GetChildCount(); + // collect new children + CollectChildren(aNote, rNewParas); + bAddNote = true; + } + else + { + // collect removed children + CollectChildren(*aItr, rOldParas); + delete aItr->mpTextHelper; + aItr->mpTextHelper = nullptr; + + // no note to add + // not necessary, because this branch should not be reached if it is the end + //if (aItr != aEndItr) + ++aItr; + } + if (bAddNote) + { + nParagraphs += aNote.mnParaCount; + rNewNotes.push_back(aNote); + bAddNote = false; + } + } + } + } + return nParagraphs; +} + +namespace { + +struct ScChildGone +{ + ScAccessibleDocumentPagePreview* mpAccDoc; + explicit ScChildGone(ScAccessibleDocumentPagePreview* pAccDoc) : mpAccDoc(pAccDoc) {} + void operator() (const uno::Reference<XAccessible>& xAccessible) const + { + if (mpAccDoc) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(mpAccDoc); + aEvent.OldValue <<= xAccessible; + aEvent.IndexHint = -1; + + mpAccDoc->CommitChange(aEvent); // gone child - event + } + } +}; + +struct ScChildNew +{ + ScAccessibleDocumentPagePreview* mpAccDoc; + explicit ScChildNew(ScAccessibleDocumentPagePreview* pAccDoc) : mpAccDoc(pAccDoc) {} + void operator() (const uno::Reference<XAccessible>& xAccessible) const + { + if (mpAccDoc) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(mpAccDoc); + aEvent.NewValue <<= xAccessible; + aEvent.IndexHint = -1; + + mpAccDoc->CommitChange(aEvent); // new child - event + } + } +}; + +} + +void ScNotesChildren::DataChanged(const tools::Rectangle& rVisRect) +{ + if (!(mpViewShell && mpAccDoc)) + return; + + ScXAccVector aNewParas; + ScXAccVector aOldParas; + ScAccNotes aNewMarks; + mnParagraphs = CheckChanges(mpViewShell->GetLocationData(), rVisRect, true, maMarks, aNewMarks, aOldParas, aNewParas); + maMarks = aNewMarks; + ScAccNotes aNewNotes; + mnParagraphs += CheckChanges(mpViewShell->GetLocationData(), rVisRect, false, maNotes, aNewNotes, aOldParas, aNewParas); + maNotes = aNewNotes; + + std::for_each(aOldParas.begin(), aOldParas.end(), ScChildGone(mpAccDoc)); + std::for_each(aNewParas.begin(), aNewParas.end(), ScChildNew(mpAccDoc)); +} + +inline ScDocument* ScNotesChildren::GetDocument() const +{ + ScDocument* pDoc = nullptr; + if (mpViewShell) + pDoc = &mpViewShell->GetDocument(); + return pDoc; +} + +namespace { + +class ScIAccessibleViewForwarder : public ::accessibility::IAccessibleViewForwarder +{ +public: + ScIAccessibleViewForwarder(); + ScIAccessibleViewForwarder(ScPreviewShell* pViewShell, + ScAccessibleDocumentPagePreview* pAccDoc, + const MapMode& aMapMode); + + ///===== IAccessibleViewForwarder ======================================== + + virtual tools::Rectangle GetVisibleArea() const override; + virtual Point LogicToPixel (const Point& rPoint) const override; + virtual Size LogicToPixel (const Size& rSize) const override; + +private: + ScPreviewShell* mpViewShell; + ScAccessibleDocumentPagePreview* mpAccDoc; + MapMode maMapMode; +}; + +} + +ScIAccessibleViewForwarder::ScIAccessibleViewForwarder() + : mpViewShell(nullptr), mpAccDoc(nullptr) +{ +} + +ScIAccessibleViewForwarder::ScIAccessibleViewForwarder(ScPreviewShell* pViewShell, + ScAccessibleDocumentPagePreview* pAccDoc, + const MapMode& aMapMode) + : mpViewShell(pViewShell), + mpAccDoc(pAccDoc), + maMapMode(aMapMode) +{ +} + +///===== IAccessibleViewForwarder ======================================== + +tools::Rectangle ScIAccessibleViewForwarder::GetVisibleArea() const +{ + SolarMutexGuard aGuard; + tools::Rectangle aVisRect; + vcl::Window* pWin = mpViewShell->GetWindow(); + if (pWin) + { + aVisRect.SetSize(pWin->GetOutputSizePixel()); + aVisRect.SetPos(Point(0, 0)); + + aVisRect = pWin->PixelToLogic(aVisRect, maMapMode); + } + + return aVisRect; +} + +Point ScIAccessibleViewForwarder::LogicToPixel (const Point& rPoint) const +{ + SolarMutexGuard aGuard; + Point aPoint; + vcl::Window* pWin = mpViewShell->GetWindow(); + if (pWin && mpAccDoc) + { + tools::Rectangle aRect(mpAccDoc->GetBoundingBoxOnScreen()); + aPoint = pWin->LogicToPixel(rPoint, maMapMode) + aRect.TopLeft(); + } + + return aPoint; +} + +Size ScIAccessibleViewForwarder::LogicToPixel (const Size& rSize) const +{ + SolarMutexGuard aGuard; + Size aSize; + vcl::Window* pWin = mpViewShell->GetWindow(); + if (pWin) + aSize = pWin->LogicToPixel(rSize, maMapMode); + return aSize; +} + +namespace { + +struct ScShapeChild +{ + ScShapeChild() + : mnRangeId(0) + { + } + ScShapeChild(ScShapeChild const &) = delete; + ScShapeChild(ScShapeChild &&) = default; + ~ScShapeChild(); + ScShapeChild & operator =(ScShapeChild const &) = delete; + ScShapeChild & operator =(ScShapeChild && other) { + std::swap(mpAccShape, other.mpAccShape); + mxShape = std::move(other.mxShape); + mnRangeId = other.mnRangeId; + return *this; + } + + mutable rtl::Reference< ::accessibility::AccessibleShape > mpAccShape; + css::uno::Reference< css::drawing::XShape > mxShape; + sal_Int32 mnRangeId; +}; + +} + +ScShapeChild::~ScShapeChild() +{ + if (mpAccShape.is()) + { + mpAccShape->dispose(); + } +} + +namespace { + +struct ScShapeChildLess +{ + bool operator()(const ScShapeChild& rChild1, const ScShapeChild& rChild2) const + { + bool bResult(false); + if (rChild1.mxShape.is() && rChild2.mxShape.is()) + bResult = (rChild1.mxShape.get() < rChild2.mxShape.get()); + return bResult; + } +}; + +} + +typedef std::vector<ScShapeChild> ScShapeChildVec; + +namespace { + +struct ScShapeRange +{ + ScShapeRange() = default; + ScShapeRange(ScShapeRange const &) = delete; + ScShapeRange(ScShapeRange &&) = default; + ScShapeRange & operator =(ScShapeRange const &) = delete; + ScShapeRange & operator =(ScShapeRange &&) = default; + + ScShapeChildVec maBackShapes; + ScShapeChildVec maForeShapes; // inclusive internal shapes + ScShapeChildVec maControls; + ScIAccessibleViewForwarder maViewForwarder; +}; + +} + +typedef std::vector<ScShapeRange> ScShapeRangeVec; + +class ScShapeChildren : public ::accessibility::IAccessibleParent +{ +public: + ScShapeChildren(ScPreviewShell* pViewShell, ScAccessibleDocumentPagePreview* pAccDoc); + + ///===== IAccessibleParent ============================================== + + virtual bool ReplaceChild ( + ::accessibility::AccessibleShape* pCurrentChild, + const css::uno::Reference< css::drawing::XShape >& _rxShape, + const tools::Long _nIndex, + const ::accessibility::AccessibleShapeTreeInfo& _rShapeTreeInfo + ) override; + + ///===== Internal ======================================================== + + void Init(); + + sal_Int32 GetBackShapeCount() const; + uno::Reference<XAccessible> GetBackShape(sal_Int32 nIndex) const; + sal_Int32 GetForeShapeCount() const; + uno::Reference<XAccessible> GetForeShape(sal_Int32 nIndex) const; + sal_Int32 GetControlCount() const; + uno::Reference<XAccessible> GetControl(sal_Int32 nIndex) const; + uno::Reference<XAccessible> GetForegroundShapeAt(const awt::Point& rPoint) const; // inclusive controls + uno::Reference<XAccessible> GetBackgroundShapeAt(const awt::Point& rPoint) const; + + void DataChanged(); + void VisAreaChanged() const; + +private: + ScAccessibleDocumentPagePreview* mpAccDoc; + ScPreviewShell* mpViewShell; + ScShapeRangeVec maShapeRanges; + + void FindChanged(ScShapeChildVec& aOld, ScShapeChildVec& aNew) const; + void FindChanged(ScShapeRange& aOld, ScShapeRange& aNew) const; + ::accessibility::AccessibleShape* GetAccShape(const ScShapeChild& rShape) const; + ::accessibility::AccessibleShape* GetAccShape(const ScShapeChildVec& rShapes, sal_Int32 nIndex) const; + void FillShapes(const tools::Rectangle& aPixelPaintRect, const MapMode& aMapMode, sal_uInt8 nRangeId); + +// void AddShape(const uno::Reference<drawing::XShape>& xShape, SdrLayerID aLayerID); +// void RemoveShape(const uno::Reference<drawing::XShape>& xShape, SdrLayerID aLayerID); + SdrPage* GetDrawPage() const; +}; + +ScShapeChildren::ScShapeChildren(ScPreviewShell* pViewShell, ScAccessibleDocumentPagePreview* pAccDoc) + : + mpAccDoc(pAccDoc), + mpViewShell(pViewShell), + maShapeRanges(SC_PREVIEW_MAXRANGES) +{ +} + +void ScShapeChildren::FindChanged(ScShapeChildVec& rOld, ScShapeChildVec& rNew) const +{ + ScShapeChildVec::iterator aOldItr = rOld.begin(); + ScShapeChildVec::iterator aOldEnd = rOld.end(); + ScShapeChildVec::const_iterator aNewItr = rNew.begin(); + ScShapeChildVec::const_iterator aNewEnd = rNew.end(); + uno::Reference<XAccessible> xAcc; + while ((aNewItr != aNewEnd) && (aOldItr != aOldEnd)) + { + if (aNewItr->mxShape.get() == aOldItr->mxShape.get()) + { + ++aOldItr; + ++aNewItr; + } + else if (aNewItr->mxShape.get() < aOldItr->mxShape.get()) + { + xAcc = GetAccShape(*aNewItr); + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference<XAccessibleContext> (mpAccDoc); + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= xAcc; + aEvent.IndexHint = -1; + mpAccDoc->CommitChange(aEvent); + ++aNewItr; + } + else + { + xAcc = GetAccShape(*aOldItr); + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference<XAccessibleContext> (mpAccDoc); + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.OldValue <<= xAcc; + aEvent.IndexHint = -1; + mpAccDoc->CommitChange(aEvent); + ++aOldItr; + } + } + while (aOldItr != aOldEnd) + { + xAcc = GetAccShape(*aOldItr); + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference<XAccessibleContext> (mpAccDoc); + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.OldValue <<= xAcc; + aEvent.IndexHint = -1; + mpAccDoc->CommitChange(aEvent); + ++aOldItr; + } + while (aNewItr != aNewEnd) + { + xAcc = GetAccShape(*aNewItr); + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference<XAccessibleContext> (mpAccDoc); + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= xAcc; + aEvent.IndexHint = -1; + mpAccDoc->CommitChange(aEvent); + ++aNewItr; + } +} + +void ScShapeChildren::FindChanged(ScShapeRange& rOld, ScShapeRange& rNew) const +{ + FindChanged(rOld.maBackShapes, rNew.maBackShapes); + FindChanged(rOld.maForeShapes, rNew.maForeShapes); + FindChanged(rOld.maControls, rNew.maControls); +} + +void ScShapeChildren::DataChanged() +{ + ScShapeRangeVec aOldShapeRanges(std::move(maShapeRanges)); + maShapeRanges.clear(); + maShapeRanges.resize(SC_PREVIEW_MAXRANGES); + Init(); + for (sal_Int32 i = 0; i < SC_PREVIEW_MAXRANGES; ++i) + { + FindChanged(aOldShapeRanges[i], maShapeRanges[i]); + } +} + +namespace +{ + struct ScVisAreaChanged + { + void operator() (const ScShapeChild& rAccShapeData) const + { + if (rAccShapeData.mpAccShape.is()) + { + rAccShapeData.mpAccShape->ViewForwarderChanged(); + } + } + }; +} + +void ScShapeChildren::VisAreaChanged() const +{ + for (auto const& shape : maShapeRanges) + { + ScVisAreaChanged aVisAreaChanged; + std::for_each(shape.maBackShapes.begin(), shape.maBackShapes.end(), aVisAreaChanged); + std::for_each(shape.maControls.begin(), shape.maControls.end(), aVisAreaChanged); + std::for_each(shape.maForeShapes.begin(), shape.maForeShapes.end(), aVisAreaChanged); + } +} + + ///===== IAccessibleParent ============================================== + +bool ScShapeChildren::ReplaceChild (::accessibility::AccessibleShape* /* pCurrentChild */, + const css::uno::Reference< css::drawing::XShape >& /* _rxShape */, + const tools::Long /* _nIndex */, const ::accessibility::AccessibleShapeTreeInfo& /* _rShapeTreeInfo */) +{ + OSL_FAIL("should not be called in the page preview"); + return false; +} + + ///===== Internal ======================================================== + +void ScShapeChildren::Init() +{ + if(!mpViewShell) + return; + + const ScPreviewLocationData& rData = mpViewShell->GetLocationData(); + MapMode aMapMode; + tools::Rectangle aPixelPaintRect; + sal_uInt8 nRangeId; + sal_uInt16 nCount(rData.GetDrawRanges()); + for (sal_uInt16 i = 0; i < nCount; ++i) + { + rData.GetDrawRange(i, aPixelPaintRect, aMapMode, nRangeId); + FillShapes(aPixelPaintRect, aMapMode, nRangeId); + } +} + +sal_Int32 ScShapeChildren::GetBackShapeCount() const +{ + sal_Int32 nCount(0); + for (auto const& shape : maShapeRanges) + nCount += shape.maBackShapes.size(); + return nCount; +} + +uno::Reference<XAccessible> ScShapeChildren::GetBackShape(sal_Int32 nIndex) const +{ + uno::Reference<XAccessible> xAccessible; + for (const auto& rShapeRange : maShapeRanges) + { + sal_Int32 nCount(rShapeRange.maBackShapes.size()); + if(nIndex < nCount) + xAccessible = GetAccShape(rShapeRange.maBackShapes, nIndex); + nIndex -= nCount; + if (xAccessible.is()) + break; + } + + if (nIndex >= 0) + throw lang::IndexOutOfBoundsException(); + + return xAccessible; +} + +sal_Int32 ScShapeChildren::GetForeShapeCount() const +{ + sal_Int32 nCount(0); + for (auto const& shape : maShapeRanges) + nCount += shape.maForeShapes.size(); + return nCount; +} + +uno::Reference<XAccessible> ScShapeChildren::GetForeShape(sal_Int32 nIndex) const +{ + uno::Reference<XAccessible> xAccessible; + for (const auto& rShapeRange : maShapeRanges) + { + sal_Int32 nCount(rShapeRange.maForeShapes.size()); + if(nIndex < nCount) + xAccessible = GetAccShape(rShapeRange.maForeShapes, nIndex); + nIndex -= nCount; + if (xAccessible.is()) + break; + } + + if (nIndex >= 0) + throw lang::IndexOutOfBoundsException(); + + return xAccessible; +} + +sal_Int32 ScShapeChildren::GetControlCount() const +{ + sal_Int32 nCount(0); + for (auto const& shape : maShapeRanges) + nCount += shape.maControls.size(); + return nCount; +} + +uno::Reference<XAccessible> ScShapeChildren::GetControl(sal_Int32 nIndex) const +{ + uno::Reference<XAccessible> xAccessible; + for (const auto& rShapeRange : maShapeRanges) + { + sal_Int32 nCount(rShapeRange.maControls.size()); + if(nIndex < nCount) + xAccessible = GetAccShape(rShapeRange.maControls, nIndex); + nIndex -= nCount; + if (xAccessible.is()) + break; + } + + if (nIndex >= 0) + throw lang::IndexOutOfBoundsException(); + + return xAccessible; +} + +namespace { + +struct ScShapePointFound +{ + Point maPoint; + explicit ScShapePointFound(const awt::Point& rPoint) : maPoint(VCLPoint(rPoint)) {} + bool operator() (const ScShapeChild& rShape) + { + bool bResult(false); + if (VCLRectangle(rShape.mpAccShape->getBounds()).Contains(maPoint)) + bResult = true; + return bResult; + } +}; + +} + +uno::Reference<XAccessible> ScShapeChildren::GetForegroundShapeAt(const awt::Point& rPoint) const //inclusive Controls +{ + uno::Reference<XAccessible> xAcc; + + for(const auto& rShapeRange : maShapeRanges) + { + ScShapeChildVec::const_iterator aFindItr = std::find_if(rShapeRange.maForeShapes.begin(), rShapeRange.maForeShapes.end(), ScShapePointFound(rPoint)); + if (aFindItr != rShapeRange.maForeShapes.end()) + xAcc = GetAccShape(*aFindItr); + else + { + ScShapeChildVec::const_iterator aCtrlItr = std::find_if(rShapeRange.maControls.begin(), rShapeRange.maControls.end(), ScShapePointFound(rPoint)); + if (aCtrlItr != rShapeRange.maControls.end()) + xAcc = GetAccShape(*aCtrlItr); + } + + if (xAcc.is()) + break; + } + + return xAcc; +} + +uno::Reference<XAccessible> ScShapeChildren::GetBackgroundShapeAt(const awt::Point& rPoint) const +{ + uno::Reference<XAccessible> xAcc; + + for(const auto& rShapeRange : maShapeRanges) + { + ScShapeChildVec::const_iterator aFindItr = std::find_if(rShapeRange.maBackShapes.begin(), rShapeRange.maBackShapes.end(), ScShapePointFound(rPoint)); + if (aFindItr != rShapeRange.maBackShapes.end()) + xAcc = GetAccShape(*aFindItr); + if (xAcc.is()) + break; + } + + return xAcc; +} + +::accessibility::AccessibleShape* ScShapeChildren::GetAccShape(const ScShapeChild& rShape) const +{ + if (!rShape.mpAccShape.is()) + { + ::accessibility::ShapeTypeHandler& rShapeHandler = ::accessibility::ShapeTypeHandler::Instance(); + ::accessibility::AccessibleShapeInfo aShapeInfo(rShape.mxShape, mpAccDoc); + + if (mpViewShell) + { + ::accessibility::AccessibleShapeTreeInfo aShapeTreeInfo; + aShapeTreeInfo.SetSdrView(mpViewShell->GetPreview()->GetDrawView()); + aShapeTreeInfo.SetController(nullptr); + aShapeTreeInfo.SetWindow(mpViewShell->GetWindow()); + aShapeTreeInfo.SetViewForwarder(&(maShapeRanges[rShape.mnRangeId].maViewForwarder)); + rShape.mpAccShape = rShapeHandler.CreateAccessibleObject(aShapeInfo, aShapeTreeInfo); + if (rShape.mpAccShape.is()) + { + rShape.mpAccShape->Init(); + } + } + } + return rShape.mpAccShape.get(); +} + +::accessibility::AccessibleShape* ScShapeChildren::GetAccShape(const ScShapeChildVec& rShapes, sal_Int32 nIndex) const +{ + return GetAccShape(rShapes[nIndex]); +} + +void ScShapeChildren::FillShapes(const tools::Rectangle& aPixelPaintRect, const MapMode& aMapMode, sal_uInt8 nRangeId) +{ + OSL_ENSURE(nRangeId < maShapeRanges.size(), "this is not a valid range for draw objects"); + SdrPage* pPage = GetDrawPage(); + vcl::Window* pWin = mpViewShell->GetWindow(); + if (!(pPage && pWin)) + return; + + bool bForeAdded(false); + bool bBackAdded(false); + bool bControlAdded(false); + tools::Rectangle aClippedPixelPaintRect(aPixelPaintRect); + if (mpAccDoc) + { + tools::Rectangle aRect2(Point(0,0), mpAccDoc->GetBoundingBoxOnScreen().GetSize()); + aClippedPixelPaintRect = aPixelPaintRect.GetIntersection(aRect2); + } + ScIAccessibleViewForwarder aViewForwarder(mpViewShell, mpAccDoc, aMapMode); + maShapeRanges[nRangeId].maViewForwarder = aViewForwarder; + for (const rtl::Reference<SdrObject>& pObj : *pPage) + { + uno::Reference< drawing::XShape > xShape(pObj->getUnoShape(), uno::UNO_QUERY); + if (xShape.is()) + { + tools::Rectangle aRect(pWin->LogicToPixel( + tools::Rectangle(VCLPoint(xShape->getPosition()), VCLSize(xShape->getSize())), aMapMode)); + if(!aClippedPixelPaintRect.GetIntersection(aRect).IsEmpty()) + { + ScShapeChild aShape; + aShape.mxShape = xShape; + aShape.mnRangeId = nRangeId; + if (pObj->GetLayer().anyOf(SC_LAYER_INTERN, SC_LAYER_FRONT)) + { + maShapeRanges[nRangeId].maForeShapes.push_back(std::move(aShape)); + bForeAdded = true; + } + else if (pObj->GetLayer() == SC_LAYER_BACK) + { + maShapeRanges[nRangeId].maBackShapes.push_back(std::move(aShape)); + bBackAdded = true; + } + else if (pObj->GetLayer() == SC_LAYER_CONTROLS) + { + maShapeRanges[nRangeId].maControls.push_back(std::move(aShape)); + bControlAdded = true; + } + else + { + OSL_FAIL("I don't know this layer."); + } + } + } + } + if (bForeAdded) + std::sort(maShapeRanges[nRangeId].maForeShapes.begin(), maShapeRanges[nRangeId].maForeShapes.end(),ScShapeChildLess()); + if (bBackAdded) + std::sort(maShapeRanges[nRangeId].maBackShapes.begin(), maShapeRanges[nRangeId].maBackShapes.end(),ScShapeChildLess()); + if (bControlAdded) + std::sort(maShapeRanges[nRangeId].maControls.begin(), maShapeRanges[nRangeId].maControls.end(),ScShapeChildLess()); +} + +SdrPage* ScShapeChildren::GetDrawPage() const +{ + SCTAB nTab( mpViewShell->GetLocationData().GetPrintTab() ); + SdrPage* pDrawPage = nullptr; + ScDocument& rDoc = mpViewShell->GetDocument(); + if (rDoc.GetDrawLayer()) + { + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + if (pDrawLayer->HasObjects() && (pDrawLayer->GetPageCount() > nTab)) + pDrawPage = pDrawLayer->GetPage(static_cast<sal_uInt16>(static_cast<sal_Int16>(nTab))); + } + return pDrawPage; +} + +namespace { + +struct ScPagePreviewCountData +{ + // order is background shapes, header, table or notes, footer, foreground shapes, controls + + tools::Rectangle aVisRect; + tools::Long nBackShapes; + tools::Long nHeaders; + tools::Long nTables; + tools::Long nNoteParagraphs; + tools::Long nFooters; + tools::Long nForeShapes; + tools::Long nControls; + + ScPagePreviewCountData( const ScPreviewLocationData& rData, const vcl::Window* pSizeWindow, + const ScNotesChildren* pNotesChildren, const ScShapeChildren* pShapeChildren ); + + tools::Long GetTotal() const + { + return nBackShapes + nHeaders + nTables + nNoteParagraphs + nFooters + nForeShapes + nControls; + } +}; + +} + +ScPagePreviewCountData::ScPagePreviewCountData( const ScPreviewLocationData& rData, + const vcl::Window* pSizeWindow, const ScNotesChildren* pNotesChildren, + const ScShapeChildren* pShapeChildren) : + nBackShapes( 0 ), + nHeaders( 0 ), + nTables( 0 ), + nNoteParagraphs( 0 ), + nFooters( 0 ), + nForeShapes( 0 ), + nControls( 0 ) +{ + Size aOutputSize; + if ( pSizeWindow ) + aOutputSize = pSizeWindow->GetOutputSizePixel(); + aVisRect = tools::Rectangle( Point(), aOutputSize ); + + tools::Rectangle aObjRect; + + if ( rData.GetHeaderPosition( aObjRect ) && aObjRect.Overlaps( aVisRect ) ) + nHeaders = 1; + + if ( rData.GetFooterPosition( aObjRect ) && aObjRect.Overlaps( aVisRect ) ) + nFooters = 1; + + if ( rData.HasCellsInRange( aVisRect ) ) + nTables = 1; + + //! shapes... + nBackShapes = pShapeChildren->GetBackShapeCount(); + nForeShapes = pShapeChildren->GetForeShapeCount(); + nControls = pShapeChildren->GetControlCount(); + + // there are only notes if there is no table + if (nTables == 0) + nNoteParagraphs = pNotesChildren->GetChildrenCount(); +} + +//===== internal ======================================================== + +ScAccessibleDocumentPagePreview::ScAccessibleDocumentPagePreview( + const uno::Reference<XAccessible>& rxParent, ScPreviewShell* pViewShell ) : + ScAccessibleDocumentBase(rxParent), + mpViewShell(pViewShell) +{ + if (pViewShell) + pViewShell->AddAccessibilityObject(*this); + +} + +ScAccessibleDocumentPagePreview::~ScAccessibleDocumentPagePreview() +{ + if (!ScAccessibleDocumentBase::IsDefunc() && !rBHelper.bInDispose) + { + // increment refcount to prevent double call off dtor + osl_atomic_increment( &m_refCount ); + // call dispose to inform object which have a weak reference to this object + dispose(); + } +} + +void SAL_CALL ScAccessibleDocumentPagePreview::disposing() +{ + SolarMutexGuard aGuard; + mpTable.clear(); + mpHeader.clear(); + mpFooter.clear(); + + if (mpViewShell) + { + mpViewShell->RemoveAccessibilityObject(*this); + mpViewShell = nullptr; + } + + // no need to Dispose the AccessibleTextHelper, + // as long as mpNotesChildren are destructed here + mpNotesChildren.reset(); + + mpShapeChildren.reset(); + + ScAccessibleDocumentBase::disposing(); +} + +//===== SfxListener ===================================================== + +void ScAccessibleDocumentPagePreview::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if ( dynamic_cast<const ScAccWinFocusLostHint*>(&rHint) ) + { + CommitFocusLost(); + } + else if ( dynamic_cast<const ScAccWinFocusGotHint*>(&rHint) ) + { + CommitFocusGained(); + } + else + { + // only notify if child exist, otherwise it is not necessary + if (rHint.GetId() == SfxHintId::ScDataChanged) + { + if (mpTable.is()) // if there is no table there is nothing to notify, because no one recognizes the change + { + { + uno::Reference<XAccessible> xAcc = mpTable; + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + aEvent.OldValue <<= xAcc; + aEvent.IndexHint = -1; + CommitChange(aEvent); + } + + mpTable->dispose(); + mpTable.clear(); + } + + Size aOutputSize; + vcl::Window* pSizeWindow = mpViewShell->GetWindow(); + if ( pSizeWindow ) + aOutputSize = pSizeWindow->GetOutputSizePixel(); + tools::Rectangle aVisRect( Point(), aOutputSize ); + GetNotesChildren()->DataChanged(aVisRect); + + GetShapeChildren()->DataChanged(); + + const ScPreviewLocationData& rData = mpViewShell->GetLocationData(); + ScPagePreviewCountData aCount( rData, mpViewShell->GetWindow(), GetNotesChildren(), GetShapeChildren() ); + + if (aCount.nTables > 0) + { + //! order is background shapes, header, table or notes, footer, foreground shapes, controls + sal_Int32 nIndex (aCount.nBackShapes + aCount.nHeaders); + + mpTable = new ScAccessiblePreviewTable( this, mpViewShell, nIndex ); + mpTable->Init(); + + { + uno::Reference<XAccessible> xAcc = mpTable; + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + aEvent.NewValue <<= xAcc; + aEvent.IndexHint = -1; + CommitChange(aEvent); + } + } + } + else if (rHint.GetId() == SfxHintId::ScAccVisAreaChanged) + { + Size aOutputSize; + vcl::Window* pSizeWindow = mpViewShell->GetWindow(); + if ( pSizeWindow ) + aOutputSize = pSizeWindow->GetOutputSizePixel(); + tools::Rectangle aVisRect( Point(), aOutputSize ); + GetNotesChildren()->DataChanged(aVisRect); + + GetShapeChildren()->VisAreaChanged(); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::VISIBLE_DATA_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + CommitChange(aEvent); + } + } + ScAccessibleDocumentBase::Notify(rBC, rHint); +} + +//===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessibleDocumentPagePreview::getAccessibleAtPoint( const awt::Point& rPoint ) +{ + uno::Reference<XAccessible> xAccessible; + if (containsPoint(rPoint)) + { + SolarMutexGuard aGuard; + IsObjectValid(); + + if ( mpViewShell ) + { + xAccessible = GetShapeChildren()->GetForegroundShapeAt(rPoint); + if (!xAccessible.is()) + { + const ScPreviewLocationData& rData = mpViewShell->GetLocationData(); + ScPagePreviewCountData aCount( rData, mpViewShell->GetWindow(), GetNotesChildren(), GetShapeChildren() ); + + if ( !mpTable.is() && (aCount.nTables > 0) ) + { + //! order is background shapes, header, table or notes, footer, foreground shapes, controls + sal_Int32 nIndex (aCount.nBackShapes + aCount.nHeaders); + + mpTable = new ScAccessiblePreviewTable( this, mpViewShell, nIndex ); + mpTable->Init(); + } + if (mpTable.is() && VCLRectangle(mpTable->getBounds()).Contains(VCLPoint(rPoint))) + xAccessible = mpTable.get(); + } + if (!xAccessible.is()) + xAccessible = GetNotesChildren()->GetAt(rPoint); + if (!xAccessible.is()) + { + if (!mpHeader.is() || !mpFooter.is()) + { + const ScPreviewLocationData& rData = mpViewShell->GetLocationData(); + ScPagePreviewCountData aCount( rData, mpViewShell->GetWindow(), GetNotesChildren(), GetShapeChildren() ); + + if (!mpHeader.is()) + { + mpHeader = new ScAccessiblePageHeader( this, mpViewShell, true, aCount.nBackShapes + aCount.nHeaders - 1); + } + if (!mpFooter.is()) + { + mpFooter = new ScAccessiblePageHeader( this, mpViewShell, false, aCount.nBackShapes + aCount.nHeaders + aCount.nTables + aCount.nNoteParagraphs + aCount.nFooters - 1 ); + } + } + + Point aPoint(VCLPoint(rPoint)); + + if (VCLRectangle(mpHeader->getBounds()).Contains(aPoint)) + xAccessible = mpHeader.get(); + else if (VCLRectangle(mpFooter->getBounds()).Contains(aPoint)) + xAccessible = mpFooter.get(); + } + if (!xAccessible.is()) + xAccessible = GetShapeChildren()->GetBackgroundShapeAt(rPoint); + } + } + + return xAccessible; +} + +void SAL_CALL ScAccessibleDocumentPagePreview::grabFocus() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleComponent> xAccessibleComponent(getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY); + if (xAccessibleComponent.is()) + { + // just grab the focus for the window + xAccessibleComponent->grabFocus(); + } + } +} + +//===== XAccessibleContext ============================================== + +sal_Int64 SAL_CALL ScAccessibleDocumentPagePreview::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + sal_Int64 nRet = 0; + if ( mpViewShell ) + { + ScPagePreviewCountData aCount( mpViewShell->GetLocationData(), mpViewShell->GetWindow(), GetNotesChildren(), GetShapeChildren() ); + nRet = aCount.GetTotal(); + } + + return nRet; +} + +uno::Reference<XAccessible> SAL_CALL ScAccessibleDocumentPagePreview::getAccessibleChild(sal_Int64 nIndex) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + uno::Reference<XAccessible> xAccessible; + + if ( mpViewShell ) + { + const ScPreviewLocationData& rData = mpViewShell->GetLocationData(); + ScPagePreviewCountData aCount( rData, mpViewShell->GetWindow(), GetNotesChildren(), GetShapeChildren() ); + + if ( nIndex < aCount.nBackShapes ) + { + xAccessible = GetShapeChildren()->GetBackShape(nIndex); + } + else if ( nIndex < aCount.nBackShapes + aCount.nHeaders ) + { + if ( !mpHeader.is() ) + { + mpHeader = new ScAccessiblePageHeader( this, mpViewShell, true, nIndex ); + } + + xAccessible = mpHeader.get(); + } + else if ( nIndex < aCount.nBackShapes + aCount.nHeaders + aCount.nTables ) + { + if ( !mpTable.is() ) + { + mpTable = new ScAccessiblePreviewTable( this, mpViewShell, nIndex ); + mpTable->Init(); + } + xAccessible = mpTable.get(); + } + else if ( nIndex < aCount.nBackShapes + aCount.nHeaders + aCount.nNoteParagraphs ) + { + xAccessible = GetNotesChildren()->GetChild(nIndex - aCount.nBackShapes - aCount.nHeaders); + } + else if ( nIndex < aCount.nBackShapes + aCount.nHeaders + aCount.nTables + aCount.nNoteParagraphs + aCount.nFooters ) + { + if ( !mpFooter.is() ) + { + mpFooter = new ScAccessiblePageHeader( this, mpViewShell, false, nIndex ); + } + xAccessible = mpFooter.get(); + } + else + { + sal_Int64 nIdx(nIndex - (aCount.nBackShapes + aCount.nHeaders + aCount.nTables + aCount.nNoteParagraphs + aCount.nFooters)); + if (nIdx < aCount.nForeShapes) + xAccessible = GetShapeChildren()->GetForeShape(nIdx); + else + xAccessible = GetShapeChildren()->GetControl(nIdx - aCount.nForeShapes); + } + } + + if ( !xAccessible.is() ) + throw lang::IndexOutOfBoundsException(); + + return xAccessible; +} + + /// Return the set of current states. +sal_Int64 SAL_CALL ScAccessibleDocumentPagePreview::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + sal_Int64 nStateSet = 0; + if (IsDefunc(nParentStates)) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + // never editable + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::OPAQUE; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + } + return nStateSet; +} + + //===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessibleDocumentPagePreview::getImplementationName() +{ + return "ScAccessibleDocumentPagePreview"; +} + +uno::Sequence< OUString> SAL_CALL ScAccessibleDocumentPagePreview::getSupportedServiceNames() +{ + const css::uno::Sequence<OUString> vals { "com.sun.star.AccessibleSpreadsheetPageView" }; + return comphelper::concatSequences(ScAccessibleContextBase::getSupportedServiceNames(), vals); +} + +//===== XTypeProvider ======================================================= + +uno::Sequence<sal_Int8> SAL_CALL + ScAccessibleDocumentPagePreview::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +//===== internal ======================================================== + +OUString ScAccessibleDocumentPagePreview::createAccessibleDescription() +{ + return STR_ACC_PREVIEWDOC_DESCR; +} + +OUString ScAccessibleDocumentPagePreview::createAccessibleName() +{ + OUString sName = ScResId(STR_ACC_PREVIEWDOC_NAME); + return sName; +} + +AbsoluteScreenPixelRectangle ScAccessibleDocumentPagePreview::GetBoundingBoxOnScreen() const +{ + AbsoluteScreenPixelRectangle aRect; + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindow(); + if (pWindow) + aRect = pWindow->GetWindowExtentsAbsolute(); + } + return aRect; +} + +tools::Rectangle ScAccessibleDocumentPagePreview::GetBoundingBox() const +{ + tools::Rectangle aRect; + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindow(); + if (pWindow) + aRect = pWindow->GetWindowExtentsRelative(*pWindow->GetAccessibleParentWindow()); + } + return aRect; +} + +bool ScAccessibleDocumentPagePreview::IsDefunc(sal_Int64 nParentStates) +{ + return ScAccessibleContextBase::IsDefunc() || !getAccessibleParent().is() || + (nParentStates & AccessibleStateType::DEFUNC); +} + +ScNotesChildren* ScAccessibleDocumentPagePreview::GetNotesChildren() +{ + if (!mpNotesChildren && mpViewShell) + { + mpNotesChildren.reset( new ScNotesChildren(mpViewShell, this) ); + + const ScPreviewLocationData& rData = mpViewShell->GetLocationData(); + ScPagePreviewCountData aCount( rData, mpViewShell->GetWindow(), GetNotesChildren(), GetShapeChildren() ); + + //! order is background shapes, header, table or notes, footer, foreground shapes, controls + mpNotesChildren->Init(aCount.aVisRect, aCount.nBackShapes + aCount.nHeaders); + } + return mpNotesChildren.get(); +} + +ScShapeChildren* ScAccessibleDocumentPagePreview::GetShapeChildren() +{ + if (!mpShapeChildren && mpViewShell) + { + mpShapeChildren.reset( new ScShapeChildren(mpViewShell, this) ); + mpShapeChildren->Init(); + } + + return mpShapeChildren.get(); +} + +OUString ScAccessibleDocumentPagePreview::getAccessibleName() +{ + SolarMutexGuard g; + + OUString aName = ScResId(STR_ACC_DOC_SPREADSHEET); + ScDocument& rScDoc = mpViewShell->GetDocument(); + + ScDocShell* pObjSh = rScDoc.GetDocumentShell(); + if (!pObjSh) + return aName; + + OUString aFileName; + SfxMedium* pMed = pObjSh->GetMedium(); + if (pMed) + aFileName = pMed->GetName(); + + if (aFileName.isEmpty()) + aFileName = pObjSh->GetTitle(SFX_TITLE_APINAME); + + if (!aFileName.isEmpty()) + { + aName = aFileName + " - " + aName + ScResId(STR_ACC_DOC_PREVIEW_SUFFIX); + + } + + return aName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleEditObject.cxx b/sc/source/ui/Accessibility/AccessibleEditObject.cxx new file mode 100644 index 0000000000..7e58af04ef --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleEditObject.cxx @@ -0,0 +1,600 @@ +/* -*- 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 <memory> +#include <utility> + +#include <AccessibleEditObject.hxx> +#include <AccessibleText.hxx> +#include <editsrc.hxx> +#include <scmod.hxx> +#include <inputhdl.hxx> +#include <inputwin.hxx> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <svx/AccessibleTextHelper.hxx> +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <svx/svdmodel.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <sfx2/objsh.hxx> +#include <cppuhelper/queryinterface.hxx> + +#include <unonames.hxx> +#include <document.hxx> +#include <AccessibleDocument.hxx> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <unotools/accessiblerelationsethelper.hxx> +#include <com/sun/star/accessibility/XAccessibleText.hpp> + +using ::com::sun::star::lang::IndexOutOfBoundsException; +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +//===== internal ============================================================ + +ScAccessibleEditObject::ScAccessibleEditObject( + const uno::Reference<XAccessible>& rxParent, + EditView* pEditView, vcl::Window* pWin, const OUString& rName, + const OUString& rDescription, EditObjectType eObjectType) + : ScAccessibleContextBase(rxParent, AccessibleRole::TEXT_FRAME) + , mpEditView(pEditView) + , mpWindow(pWin) + , mpTextWnd(nullptr) + , meObjectType(eObjectType) + , mbHasFocus(false) + , m_pScDoc(nullptr) +{ + InitAcc(rxParent, pEditView, rName, rDescription); +} + +ScAccessibleEditObject::ScAccessibleEditObject(EditObjectType eObjectType) + : ScAccessibleContextBase(nullptr, AccessibleRole::TEXT_FRAME) + , mpEditView(nullptr) + , mpWindow(nullptr) + , mpTextWnd(nullptr) + , meObjectType(eObjectType) + , mbHasFocus(false) + , m_pScDoc(nullptr) +{ +} + +void ScAccessibleEditObject::InitAcc( + const uno::Reference<XAccessible>& rxParent, + EditView* pEditView, + const OUString& rName, + const OUString& rDescription) +{ + SetParent(rxParent); + mpEditView = pEditView; + + CreateTextHelper(); + SetName(rName); + SetDescription(rDescription); + if( meObjectType == CellInEditMode) + { + const ScAccessibleDocument *pAccDoc = static_cast<ScAccessibleDocument*>(rxParent.get()); + if (pAccDoc) + { + m_pScDoc = pAccDoc->GetDocument(); + m_curCellAddress =pAccDoc->GetCurCellAddress(); + } + } +} + +ScAccessibleEditObject::~ScAccessibleEditObject() +{ + if (!ScAccessibleContextBase::IsDefunc() && !rBHelper.bInDispose) + { + // increment refcount to prevent double call off dtor + osl_atomic_increment( &m_refCount ); + // call dispose to inform object which have a weak reference to this object + dispose(); + } +} + +void SAL_CALL ScAccessibleEditObject::disposing() +{ + SolarMutexGuard aGuard; + mpTextHelper.reset(); + + ScAccessibleContextBase::disposing(); +} + +void ScAccessibleEditObject::LostFocus() +{ + mbHasFocus = false; + if (mpTextHelper) + mpTextHelper->SetFocus(false); + CommitFocusLost(); +} + +void ScAccessibleEditObject::GotFocus() +{ + mbHasFocus = true; + CommitFocusGained(); + if (mpTextHelper) + mpTextHelper->SetFocus(); +} + +//===== XInterface ========================================================== + +css::uno::Any SAL_CALL + ScAccessibleEditObject::queryInterface (const css::uno::Type & rType) +{ + css::uno::Any aReturn = ScAccessibleContextBase::queryInterface (rType); + if ( ! aReturn.hasValue()) + aReturn = ::cppu::queryInterface (rType, + static_cast< css::accessibility::XAccessibleSelection* >(this) + ); + return aReturn; +} +void SAL_CALL + ScAccessibleEditObject::acquire() + noexcept +{ + ScAccessibleContextBase::acquire (); +} +void SAL_CALL + ScAccessibleEditObject::release() + noexcept +{ + ScAccessibleContextBase::release (); +} + //===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessibleEditObject::getAccessibleAtPoint( + const awt::Point& rPoint ) +{ + uno::Reference<XAccessible> xRet; + if (containsPoint(rPoint)) + { + SolarMutexGuard aGuard; + IsObjectValid(); + + CreateTextHelper(); + + xRet = mpTextHelper->GetAt(rPoint); + } + + return xRet; +} + +AbsoluteScreenPixelRectangle ScAccessibleEditObject::GetBoundingBoxOnScreen() const +{ + AbsoluteScreenPixelRectangle aScreenBounds; + + if ( mpWindow ) + { + if ( meObjectType == CellInEditMode ) + { + if ( mpEditView && mpEditView->GetEditEngine() ) + { + MapMode aMapMode( mpEditView->GetEditEngine()->GetRefMapMode() ); + tools::Rectangle aScreenBoundsLog = mpWindow->LogicToPixel( mpEditView->GetOutputArea(), aMapMode ); + Point aCellLoc = aScreenBoundsLog.TopLeft(); + AbsoluteScreenPixelRectangle aWindowRect = mpWindow->GetWindowExtentsAbsolute(); + AbsoluteScreenPixelPoint aWindowLoc = aWindowRect.TopLeft(); + AbsoluteScreenPixelPoint aPos( aCellLoc.getX() + aWindowLoc.getX(), aCellLoc.getY() + aWindowLoc.getY() ); + aScreenBounds = AbsoluteScreenPixelRectangle( aPos, aScreenBoundsLog.GetSize() ); + } + } + else + { + aScreenBounds = mpWindow->GetWindowExtentsAbsolute(); + } + } + + return aScreenBounds; +} + +tools::Rectangle ScAccessibleEditObject::GetBoundingBox() const +{ + tools::Rectangle aBounds( GetBoundingBoxOnScreen() ); + + if ( mpWindow ) + { + uno::Reference< XAccessible > xThis( mpWindow->GetAccessible() ); + if ( xThis.is() ) + { + uno::Reference< XAccessibleContext > xContext( xThis->getAccessibleContext() ); + if ( xContext.is() ) + { + uno::Reference< XAccessible > xParent( xContext->getAccessibleParent() ); + if ( xParent.is() ) + { + uno::Reference< XAccessibleComponent > xParentComponent( xParent->getAccessibleContext(), uno::UNO_QUERY ); + if ( xParentComponent.is() ) + { + Point aScreenLoc = aBounds.TopLeft(); + awt::Point aParentScreenLoc = xParentComponent->getLocationOnScreen(); + Point aPos( aScreenLoc.getX() - aParentScreenLoc.X, aScreenLoc.getY() - aParentScreenLoc.Y ); + aBounds.SetPos( aPos ); + } + } + } + } + } + + return aBounds; +} + + //===== XAccessibleContext ============================================== + +sal_Int64 SAL_CALL + ScAccessibleEditObject::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + CreateTextHelper(); + return mpTextHelper->GetChildCount(); +} + +uno::Reference< XAccessible > SAL_CALL + ScAccessibleEditObject::getAccessibleChild(sal_Int64 nIndex) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + CreateTextHelper(); + return mpTextHelper->GetChild(nIndex); +} + +sal_Int64 SAL_CALL ScAccessibleEditObject::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + sal_Int64 nStateSet = 0; + if (IsDefunc(nParentStates)) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + // all states are const, because this object exists only in one state + nStateSet |= AccessibleStateType::EDITABLE; + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::SENSITIVE; + nStateSet |= AccessibleStateType::MULTI_LINE; + nStateSet |= AccessibleStateType::MULTI_SELECTABLE; + nStateSet |= AccessibleStateType::SHOWING; + nStateSet |= AccessibleStateType::VISIBLE; + } + return nStateSet; +} + +OUString + ScAccessibleEditObject::createAccessibleDescription() +{ +// OSL_FAIL("Should never be called, because is set in the constructor.") + return OUString(); +} + +OUString + ScAccessibleEditObject::createAccessibleName() +{ + OSL_FAIL("Should never be called, because is set in the constructor."); + return OUString(); +} + + ///===== XAccessibleEventBroadcaster ===================================== + +void SAL_CALL + ScAccessibleEditObject::addAccessibleEventListener(const uno::Reference<XAccessibleEventListener>& xListener) +{ + CreateTextHelper(); + + mpTextHelper->AddEventListener(xListener); + + ScAccessibleContextBase::addAccessibleEventListener(xListener); +} + +void SAL_CALL + ScAccessibleEditObject::removeAccessibleEventListener(const uno::Reference<XAccessibleEventListener>& xListener) +{ + CreateTextHelper(); + + mpTextHelper->RemoveEventListener(xListener); + + ScAccessibleContextBase::removeAccessibleEventListener(xListener); +} + + //===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessibleEditObject::getImplementationName() +{ + return "ScAccessibleEditObject"; +} + +//===== XTypeProvider ======================================================= + +uno::Sequence<sal_Int8> SAL_CALL + ScAccessibleEditObject::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + + //==== internal ========================================================= + +bool ScAccessibleEditObject::IsDefunc(sal_Int64 nParentStates) +{ + return ScAccessibleContextBase::IsDefunc() || !getAccessibleParent().is() || + (nParentStates & AccessibleStateType::DEFUNC); +} + +OutputDevice* ScAccessibleEditObject::GetOutputDeviceForView() +{ + return mpWindow->GetOutDev(); +} + +void ScAccessibleEditObject::CreateTextHelper() +{ + if (mpTextHelper) + return; + + ::std::unique_ptr < ScAccessibleTextData > pAccessibleTextData; + if (meObjectType == CellInEditMode || meObjectType == EditControl) + { + pAccessibleTextData.reset + (new ScAccessibleEditObjectTextData(mpEditView, GetOutputDeviceForView())); + } + else + { + pAccessibleTextData.reset + (new ScAccessibleEditLineTextData(nullptr, GetOutputDeviceForView(), mpTextWnd)); + } + + std::unique_ptr<ScAccessibilityEditSource> pEditSrc = + std::make_unique<ScAccessibilityEditSource>(std::move(pAccessibleTextData)); + + mpTextHelper = std::make_unique<::accessibility::AccessibleTextHelper>(std::move(pEditSrc)); + mpTextHelper->SetEventSource(this); + + const ScInputHandler* pInputHdl = SC_MOD()->GetInputHdl(); + if ( pInputHdl && pInputHdl->IsEditMode() ) + { + mpTextHelper->SetFocus(); + } + else + { + mpTextHelper->SetFocus(mbHasFocus); + } + + // #i54814# activate cell in edit mode + if( meObjectType == CellInEditMode ) + { + // do not activate cell object, if top edit line is active + if( pInputHdl && !pInputHdl->IsTopMode() ) + { + SdrHint aHint( SdrHintKind::BeginEdit ); + mpTextHelper->GetEditSource().GetBroadcaster().Broadcast( aHint ); + } + } +} + +sal_Int32 SAL_CALL ScAccessibleEditObject::getForeground( ) +{ + return GetFgBgColor(SC_UNONAME_CCOLOR); +} + +sal_Int32 SAL_CALL ScAccessibleEditObject::getBackground( ) +{ + return GetFgBgColor(SC_UNONAME_CELLBACK); +} + +sal_Int32 ScAccessibleEditObject::GetFgBgColor( const OUString &strPropColor) +{ + SolarMutexGuard aGuard; + sal_Int32 nColor(0); + if (m_pScDoc) + { + ScDocShell* pObjSh = m_pScDoc->GetDocumentShell(); + if ( pObjSh ) + { + ScModelObj* pSpreadDoc = pObjSh->GetModel(); + if ( pSpreadDoc ) + { + uno::Reference<sheet::XSpreadsheets> xSheets = pSpreadDoc->getSheets(); + uno::Reference<container::XIndexAccess> xIndex( xSheets, uno::UNO_QUERY ); + if ( xIndex.is() ) + { + uno::Any aTable = xIndex->getByIndex(m_curCellAddress.Tab()); + uno::Reference<sheet::XSpreadsheet> xTable; + if (aTable>>=xTable) + { + uno::Reference<table::XCell> xCell = xTable->getCellByPosition(m_curCellAddress.Col(), m_curCellAddress.Row()); + if (xCell.is()) + { + uno::Reference<beans::XPropertySet> xCellProps(xCell, uno::UNO_QUERY); + if (xCellProps.is()) + { + uno::Any aAny = xCellProps->getPropertyValue(strPropColor); + aAny >>= nColor; + } + } + } + } + } + } + } + return nColor; +} +//===== XAccessibleSelection ============================================ + +void SAL_CALL ScAccessibleEditObject::selectAccessibleChild( sal_Int64 ) +{ +} + +sal_Bool SAL_CALL ScAccessibleEditObject::isAccessibleChildSelected( sal_Int64 nChildIndex ) +{ + 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 ScAccessibleEditObject::clearAccessibleSelection( ) +{ +} + +void SAL_CALL ScAccessibleEditObject::selectAllAccessibleChildren( ) +{ +} + +sal_Int64 SAL_CALL ScAccessibleEditObject::getSelectedAccessibleChildCount() +{ + sal_Int64 nCount = 0; + sal_Int64 TotalCount = getAccessibleChildCount(); + for( sal_Int64 i = 0; i < TotalCount; i++ ) + if( isAccessibleChildSelected(i) ) nCount++; + return nCount; +} + +uno::Reference<XAccessible> SAL_CALL ScAccessibleEditObject::getSelectedAccessibleChild( sal_Int64 nSelectedChildIndex ) +{ + if ( nSelectedChildIndex < 0 || nSelectedChildIndex > getSelectedAccessibleChildCount() ) + throw IndexOutOfBoundsException(); + + for (sal_Int64 i1 = 0, i2 = 0; i1 < getAccessibleChildCount(); i1++ ) + if( isAccessibleChildSelected(i1) ) + { + if( i2 == nSelectedChildIndex ) + return getAccessibleChild( i1 ); + i2++; + } + return uno::Reference<XAccessible>(); +} + +void SAL_CALL ScAccessibleEditObject::deselectAccessibleChild(sal_Int64) +{ +} + +uno::Reference< XAccessibleRelationSet > ScAccessibleEditObject::getAccessibleRelationSet( ) +{ + SolarMutexGuard aGuard; + vcl::Window* pWindow = mpWindow; + rtl::Reference<utl::AccessibleRelationSetHelper> rRelationSet = new utl::AccessibleRelationSetHelper; + if ( pWindow ) + { + vcl::Window *pLabeledBy = pWindow->GetAccessibleRelationLabeledBy(); + if ( pLabeledBy && pLabeledBy != pWindow ) + { + uno::Sequence< uno::Reference< uno::XInterface > > aSequence { pLabeledBy->GetAccessible() }; + rRelationSet->AddRelation( AccessibleRelation( AccessibleRelationType::LABELED_BY, aSequence ) ); + } + vcl::Window* pMemberOf = pWindow->GetAccessibleRelationMemberOf(); + if ( pMemberOf && pMemberOf != pWindow ) + { + uno::Sequence< uno::Reference< uno::XInterface > > aSequence { pMemberOf->GetAccessible() }; + rRelationSet->AddRelation( AccessibleRelation( AccessibleRelationType::MEMBER_OF, aSequence ) ); + } + return rRelationSet; + } + return uno::Reference< XAccessibleRelationSet >(); +} + +AbsoluteScreenPixelRectangle ScAccessibleEditControlObject::GetBoundingBoxOnScreen() const +{ + AbsoluteScreenPixelRectangle aScreenBounds; + + if (m_pController && m_pController->GetDrawingArea()) + { + aScreenBounds = AbsoluteScreenPixelRectangle(m_pController->GetDrawingArea()->get_accessible_location_on_screen(), + m_pController->GetOutputSizePixel()); + } + + return aScreenBounds; +} + +tools::Rectangle ScAccessibleEditControlObject::GetBoundingBox() const +{ + tools::Rectangle aBounds( GetBoundingBoxOnScreen() ); + + uno::Reference< XAccessibleContext > xContext(const_cast<ScAccessibleEditControlObject*>(this)->getAccessibleContext()); + if ( xContext.is() ) + { + uno::Reference< XAccessible > xParent( xContext->getAccessibleParent() ); + if ( xParent.is() ) + { + uno::Reference< XAccessibleComponent > xParentComponent( xParent->getAccessibleContext(), uno::UNO_QUERY ); + if ( xParentComponent.is() ) + { + Point aScreenLoc = aBounds.TopLeft(); + awt::Point aParentScreenLoc = xParentComponent->getLocationOnScreen(); + Point aPos( aScreenLoc.getX() - aParentScreenLoc.X, aScreenLoc.getY() - aParentScreenLoc.Y ); + aBounds.SetPos( aPos ); + } + } + } + + return aBounds; +} + +void SAL_CALL ScAccessibleEditControlObject::disposing() +{ + ScAccessibleEditObject::disposing(); + m_pController = nullptr; +} + +uno::Reference< XAccessibleRelationSet > ScAccessibleEditControlObject::getAccessibleRelationSet() +{ + SolarMutexGuard aGuard; + if (!m_pController || !m_pController->GetDrawingArea()) + return uno::Reference< XAccessibleRelationSet >(); + return m_pController->GetDrawingArea()->get_accessible_relation_set(); +} + +OutputDevice* ScAccessibleEditControlObject::GetOutputDeviceForView() +{ + if (!m_pController || !m_pController->GetDrawingArea()) + return nullptr; + return &m_pController->GetDrawingArea()->get_ref_device(); +} + +ScAccessibleEditLineObject::ScAccessibleEditLineObject(ScTextWnd* pTextWnd) + : ScAccessibleEditControlObject(pTextWnd, ScAccessibleEditObject::EditLine) +{ + // tdf#141769 set this early so its always available, even before the on-demand + // editview is created + mpTextWnd = pTextWnd; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessiblePageHeader.cxx b/sc/source/ui/Accessibility/AccessiblePageHeader.cxx new file mode 100644 index 0000000000..a79017276b --- /dev/null +++ b/sc/source/ui/Accessibility/AccessiblePageHeader.cxx @@ -0,0 +1,372 @@ +/* -*- 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 <AccessiblePageHeader.hxx> +#include <AccessiblePageHeaderArea.hxx> +#include <prevwsh.hxx> +#include <prevloc.hxx> +#include <document.hxx> +#include <stlpool.hxx> +#include <scitems.hxx> +#include <attrib.hxx> +#include <scresid.hxx> +#include <strings.hrc> +#include <strings.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 <comphelper/sequence.hxx> + +#include <vcl/window.hxx> +#include <svl/hint.hxx> +#include <svl/itemset.hxx> +#include <vcl/svapp.hxx> +#include <svl/style.hxx> +#include <editeng/editobj.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +const sal_uInt8 MAX_AREAS = 3; + +ScAccessiblePageHeader::ScAccessiblePageHeader( const css::uno::Reference<css::accessibility::XAccessible>& rxParent, + ScPreviewShell* pViewShell, bool bHeader, sal_Int32 nIndex ) : +ScAccessibleContextBase( rxParent, bHeader ? AccessibleRole::HEADER : AccessibleRole::FOOTER ), + mpViewShell( pViewShell ), + mnIndex( nIndex ), + mbHeader( bHeader ), + maAreas(MAX_AREAS, rtl::Reference<ScAccessiblePageHeaderArea>()), + mnChildCount(-1) +{ + if (mpViewShell) + mpViewShell->AddAccessibilityObject(*this); +} + +ScAccessiblePageHeader::~ScAccessiblePageHeader() +{ + if (!ScAccessibleContextBase::IsDefunc() && !rBHelper.bInDispose) + { + // increment refcount to prevent double call off dtor + osl_atomic_increment( &m_refCount ); + dispose(); + } +} + +void SAL_CALL ScAccessiblePageHeader::disposing() +{ + SolarMutexGuard aGuard; + if (mpViewShell) + { + mpViewShell->RemoveAccessibilityObject(*this); + mpViewShell = nullptr; + } + for (auto & i : maAreas) + { + if (i.is()) + { + i->dispose(); + i.clear(); + } + } + + ScAccessibleContextBase::disposing(); +} + +//===== SfxListener ===================================================== + +void ScAccessiblePageHeader::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + // only notify if child exist, otherwise it is not necessary + if (rHint.GetId() == SfxHintId::ScDataChanged) + { + std::vector<rtl::Reference<ScAccessiblePageHeaderArea>> aOldAreas(maAreas); + mnChildCount = -1; + getAccessibleChildCount(); + for (sal_uInt8 i = 0; i < MAX_AREAS; ++i) + { + if ((aOldAreas[i].is() && maAreas[i].is() && !ScGlobal::EETextObjEqual(aOldAreas[i]->GetEditTextObject(), maAreas[i]->GetEditTextObject())) || + (aOldAreas[i].is() && !maAreas[i].is()) || (!aOldAreas[i].is() && maAreas[i].is())) + { + if (aOldAreas[i].is() && aOldAreas[i]->GetEditTextObject()) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + aEvent.OldValue <<= uno::Reference<XAccessible>(aOldAreas[i]); + aEvent.IndexHint = -1; + + CommitChange(aEvent); // child gone - event + aOldAreas[i]->dispose(); + } + if (maAreas[i].is() && maAreas[i]->GetEditTextObject()) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + aEvent.NewValue <<= uno::Reference<XAccessible>(maAreas[i]); + aEvent.IndexHint = -1; + + CommitChange(aEvent); // new child - event + } + } + } + } + else if (rHint.GetId() == SfxHintId::ScAccVisAreaChanged) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::VISIBLE_DATA_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + CommitChange(aEvent); + } + + ScAccessibleContextBase::Notify(rBC, rHint); +} + +//===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePageHeader::getAccessibleAtPoint( const awt::Point& aPoint ) +{ + uno::Reference<XAccessible> xRet; + + if (containsPoint(aPoint)) + { + SolarMutexGuard aGuard; + IsObjectValid(); + + sal_Int64 nCount(getAccessibleChildCount()); // fill the areas + + if (nCount) + { + // return the first with content, because they have all the same Bounding Box + sal_uInt8 i(0); + while(!xRet.is() && i < MAX_AREAS) + { + if (maAreas[i].is()) + xRet = maAreas[i].get(); + else + ++i; + } + } + } + + return xRet; +} + +void SAL_CALL ScAccessiblePageHeader::grabFocus() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleComponent> xAccessibleComponent(getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY); + if (xAccessibleComponent.is()) + xAccessibleComponent->grabFocus(); + } +} + +//===== XAccessibleContext ============================================== + +sal_Int64 SAL_CALL ScAccessiblePageHeader::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if((mnChildCount < 0) && mpViewShell) + { + mnChildCount = 0; + ScDocument& rDoc = mpViewShell->GetDocument(); + // find out how many regions (left,center, right) are with content + + SfxStyleSheetBase* pStyle = rDoc.GetStyleSheetPool()->Find(rDoc.GetPageStyle(mpViewShell->GetLocationData().GetPrintTab()), SfxStyleFamily::Page); + if (pStyle) + { + sal_uInt16 nPageWhichId(0); + if (mbHeader) + nPageWhichId = mpViewShell->GetLocationData().IsHeaderLeft() ? ATTR_PAGE_HEADERLEFT : ATTR_PAGE_HEADERRIGHT; + else + nPageWhichId = mpViewShell->GetLocationData().IsFooterLeft() ? ATTR_PAGE_FOOTERLEFT : ATTR_PAGE_FOOTERRIGHT; + + const ScPageHFItem& rPageItem = static_cast<const ScPageHFItem&>(pStyle->GetItemSet().Get(nPageWhichId)); + AddChild(rPageItem.GetLeftArea(), 0, SvxAdjust::Left); + AddChild(rPageItem.GetCenterArea(), 1, SvxAdjust::Center); + AddChild(rPageItem.GetRightArea(), 2, SvxAdjust::Right); + } + } + + return mnChildCount; +} + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePageHeader::getAccessibleChild( sal_Int64 nIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + uno::Reference<XAccessible> xRet; + + if(mnChildCount < 0) + getAccessibleChildCount(); + + if (nIndex >= 0) + for (const auto& rxArea : maAreas) + { + if (rxArea.is()) + { + if (nIndex == 0) + { + xRet = rxArea.get(); + break; + } + else + --nIndex; + } + } + + if ( !xRet.is() ) + throw lang::IndexOutOfBoundsException(); + + return xRet; +} + +sal_Int64 SAL_CALL ScAccessiblePageHeader::getAccessibleIndexInParent() +{ + return mnIndex; +} + +sal_Int64 SAL_CALL ScAccessiblePageHeader::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + sal_Int64 nStateSet = 0; + if (IsDefunc(nParentStates)) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::OPAQUE; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + } + return nStateSet; +} + +//===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessiblePageHeader::getImplementationName() +{ + return "ScAccessiblePageHeader"; +} + +uno::Sequence<OUString> SAL_CALL ScAccessiblePageHeader::getSupportedServiceNames() +{ + const css::uno::Sequence<OUString> vals { "com.sun.star.text.AccessibleHeaderFooterView" }; + return comphelper::concatSequences(ScAccessibleContextBase::getSupportedServiceNames(), vals); +} + +//==== internal ========================================================= + +OUString ScAccessiblePageHeader::createAccessibleDescription() +{ + OUString sDesc(mbHeader ? STR_ACC_HEADER_DESCR : STR_ACC_FOOTER_DESCR); + return sDesc.replaceFirst("%1", ScResId(SCSTR_UNKNOWN)); +} + +OUString ScAccessiblePageHeader::createAccessibleName() +{ + OUString sName(ScResId(mbHeader ? STR_ACC_HEADER_NAME : STR_ACC_FOOTER_NAME)); + return sName.replaceFirst("%1", ScResId(SCSTR_UNKNOWN)); +} + +AbsoluteScreenPixelRectangle ScAccessiblePageHeader::GetBoundingBoxOnScreen() const +{ + tools::Rectangle aCellRect(GetBoundingBox()); + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindow(); + if (pWindow) + { + AbsoluteScreenPixelRectangle aRect = pWindow->GetWindowExtentsAbsolute(); + aCellRect.Move(aRect.Left(), aRect.Top()); + } + } + return AbsoluteScreenPixelRectangle(aCellRect); +} + +tools::Rectangle ScAccessiblePageHeader::GetBoundingBox() const +{ + tools::Rectangle aRect; + if (mpViewShell) + { + const ScPreviewLocationData& rData = mpViewShell->GetLocationData(); + if ( mbHeader ) + rData.GetHeaderPosition( aRect ); + else + rData.GetFooterPosition( aRect ); + + // the Rectangle could contain negative coordinates so it should be clipped + tools::Rectangle aClipRect(Point(0, 0), aRect.GetSize()); + vcl::Window* pWindow = mpViewShell->GetWindow(); + if (pWindow) + aClipRect = pWindow->GetWindowExtentsRelative(*pWindow->GetAccessibleParentWindow()); + aRect = aClipRect.GetIntersection(aRect); + } + if (aRect.IsEmpty()) + aRect.SetSize(Size(-1, -1)); + + return aRect; +} + +bool ScAccessiblePageHeader::IsDefunc( sal_Int64 nParentStates ) +{ + return ScAccessibleContextBase::IsDefunc() || (mpViewShell == nullptr) || !getAccessibleParent().is() || + (nParentStates & AccessibleStateType::DEFUNC); +} + +void ScAccessiblePageHeader::AddChild(const EditTextObject* pArea, sal_uInt32 nIndex, SvxAdjust eAdjust) +{ + if (pArea && (!pArea->GetText(0).isEmpty() || (pArea->GetParagraphCount() > 1))) + { + if (maAreas[nIndex].is()) + { + if (!ScGlobal::EETextObjEqual(maAreas[nIndex]->GetEditTextObject(), pArea)) + { + maAreas[nIndex] = new ScAccessiblePageHeaderArea(this, mpViewShell, pArea, eAdjust); + } + } + else + { + maAreas[nIndex] = new ScAccessiblePageHeaderArea(this, mpViewShell, pArea, eAdjust); + } + ++mnChildCount; + } + else + { + maAreas[nIndex].clear(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessiblePageHeaderArea.cxx b/sc/source/ui/Accessibility/AccessiblePageHeaderArea.cxx new file mode 100644 index 0000000000..ec1fbf3add --- /dev/null +++ b/sc/source/ui/Accessibility/AccessiblePageHeaderArea.cxx @@ -0,0 +1,273 @@ +/* -*- 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 <tools/gen.hxx> +#include <AccessiblePageHeaderArea.hxx> +#include <AccessibleText.hxx> +#include <editsrc.hxx> +#include <prevwsh.hxx> +#include <scresid.hxx> +#include <strings.hrc> +#include <strings.hxx> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <comphelper/sequence.hxx> +#include <editeng/editobj.hxx> +#include <svx/AccessibleTextHelper.hxx> +#include <toolkit/helper/convert.hxx> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + + //===== internal ======================================================== + +ScAccessiblePageHeaderArea::ScAccessiblePageHeaderArea( + const uno::Reference<XAccessible>& rxParent, + ScPreviewShell* pViewShell, + const EditTextObject* pEditObj, + SvxAdjust eAdjust) + : ScAccessibleContextBase(rxParent, AccessibleRole::TEXT), + mpEditObj(pEditObj->Clone()), + mpViewShell(pViewShell), + meAdjust(eAdjust) +{ + if (mpViewShell) + mpViewShell->AddAccessibilityObject(*this); +} + +ScAccessiblePageHeaderArea::~ScAccessiblePageHeaderArea() +{ + if (!ScAccessibleContextBase::IsDefunc() && !rBHelper.bInDispose) + { + // increment refcount to prevent double call off dtor + osl_atomic_increment( &m_refCount ); + dispose(); + } +} + +void SAL_CALL ScAccessiblePageHeaderArea::disposing() +{ + SolarMutexGuard aGuard; + if (mpViewShell) + { + mpViewShell->RemoveAccessibilityObject(*this); + mpViewShell = nullptr; + } + mpTextHelper.reset(); + mpEditObj.reset(); + ScAccessibleContextBase::disposing(); +} + +//===== SfxListener ===================================================== + +void ScAccessiblePageHeaderArea::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + // only notify if child exist, otherwise it is not necessary + if (rHint.GetId() == SfxHintId::ScAccVisAreaChanged) + { + if (mpTextHelper) + mpTextHelper->UpdateChildren(); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::VISIBLE_DATA_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + CommitChange(aEvent); + } + ScAccessibleContextBase::Notify(rBC, rHint); +} + //===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePageHeaderArea::getAccessibleAtPoint( + const awt::Point& rPoint ) +{ + uno::Reference<XAccessible> xRet; + if (containsPoint(rPoint)) + { + SolarMutexGuard aGuard; + IsObjectValid(); + + if(!mpTextHelper) + CreateTextHelper(); + + xRet = mpTextHelper->GetAt(rPoint); + } + + return xRet; +} + + //===== XAccessibleContext ============================================== + +sal_Int64 SAL_CALL + ScAccessiblePageHeaderArea::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (!mpTextHelper) + CreateTextHelper(); + return mpTextHelper->GetChildCount(); +} + +uno::Reference< XAccessible > SAL_CALL + ScAccessiblePageHeaderArea::getAccessibleChild(sal_Int64 nIndex) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (!mpTextHelper) + CreateTextHelper(); + return mpTextHelper->GetChild(nIndex); +} + +sal_Int64 SAL_CALL ScAccessiblePageHeaderArea::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nStateSet = 0; + if (IsDefunc()) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::MULTI_LINE; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + } + return nStateSet; +} + +// XServiceInfo + +OUString SAL_CALL + ScAccessiblePageHeaderArea::getImplementationName() +{ + return "ScAccessiblePageHeaderArea"; +} + +uno::Sequence< OUString> SAL_CALL + ScAccessiblePageHeaderArea::getSupportedServiceNames() +{ + const css::uno::Sequence<OUString> vals { "com.sun.star.sheet.AccessiblePageHeaderFooterAreasView" }; + return comphelper::concatSequences(ScAccessibleContextBase::getSupportedServiceNames(), vals); +} + +//===== XTypeProvider ======================================================= + +uno::Sequence<sal_Int8> SAL_CALL + ScAccessiblePageHeaderArea::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +//===== internal ============================================================== +OUString ScAccessiblePageHeaderArea::createAccessibleDescription() +{ + OUString sDesc; + switch (meAdjust) + { + case SvxAdjust::Left : + sDesc = STR_ACC_LEFTAREA_DESCR; + break; + case SvxAdjust::Right: + sDesc = STR_ACC_RIGHTAREA_DESCR; + break; + case SvxAdjust::Center: + sDesc = STR_ACC_CENTERAREA_DESCR; + break; + default: + OSL_FAIL("wrong adjustment found"); + } + + return sDesc; +} + +OUString ScAccessiblePageHeaderArea::createAccessibleName() +{ + OUString sName; + switch (meAdjust) + { + case SvxAdjust::Left : + sName = ScResId(STR_ACC_LEFTAREA_NAME); + break; + case SvxAdjust::Right: + sName = ScResId(STR_ACC_RIGHTAREA_NAME); + break; + case SvxAdjust::Center: + sName = ScResId(STR_ACC_CENTERAREA_NAME); + break; + default: + OSL_FAIL("wrong adjustment found"); + } + + return sName; +} + +AbsoluteScreenPixelRectangle ScAccessiblePageHeaderArea::GetBoundingBoxOnScreen() const +{ + AbsoluteScreenPixelRectangle aRect; + if (mxParent.is()) + { + uno::Reference<XAccessibleContext> xContext = mxParent->getAccessibleContext(); + uno::Reference<XAccessibleComponent> xComp(xContext, uno::UNO_QUERY); + if (xComp.is()) + { + // has the same size and position on screen like the parent + aRect = AbsoluteScreenPixelRectangle( + AbsoluteScreenPixelPoint(VCLPoint(xComp->getLocationOnScreen())), + AbsoluteScreenPixelSize(VCLRectangle(xComp->getBounds()).GetSize())); + } + } + return aRect; +} + +tools::Rectangle ScAccessiblePageHeaderArea::GetBoundingBox() const +{ + tools::Rectangle aRect; + if (mxParent.is()) + { + uno::Reference<XAccessibleContext> xContext = mxParent->getAccessibleContext(); + uno::Reference<XAccessibleComponent> xComp(xContext, uno::UNO_QUERY); + if (xComp.is()) + { + // has the same size and position on screen like the parent and so the pos is (0, 0) + tools::Rectangle aNewRect(Point(0, 0), VCLRectangle(xComp->getBounds()).GetSize()); + aRect = aNewRect; + } + } + + return aRect; +} + +void ScAccessiblePageHeaderArea::CreateTextHelper() +{ + if (!mpTextHelper) + { + mpTextHelper.reset( new ::accessibility::AccessibleTextHelper( + std::make_unique<ScAccessibilityEditSource>( + std::make_unique<ScAccessibleHeaderTextData>( + mpViewShell, mpEditObj.get(), meAdjust))) ); + mpTextHelper->SetEventSource(this); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessiblePreviewCell.cxx b/sc/source/ui/Accessibility/AccessiblePreviewCell.cxx new file mode 100644 index 0000000000..3c4d334305 --- /dev/null +++ b/sc/source/ui/Accessibility/AccessiblePreviewCell.cxx @@ -0,0 +1,272 @@ +/* -*- 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 <scitems.hxx> +#include <tools/gen.hxx> +#include <AccessibleText.hxx> +#include <editsrc.hxx> +#include <AccessiblePreviewCell.hxx> +#include <prevwsh.hxx> +#include <prevloc.hxx> +#include <document.hxx> +#include <svx/AccessibleTextHelper.hxx> +#include <editeng/brushitem.hxx> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <toolkit/helper/convert.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <comphelper/sequence.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +//===== internal ============================================================ + +ScAccessiblePreviewCell::ScAccessiblePreviewCell( const css::uno::Reference<css::accessibility::XAccessible>& rxParent, + ScPreviewShell* pViewShell, + const ScAddress& rCellAddress, + sal_Int32 nIndex ) : + ScAccessibleCellBase( rxParent, ( pViewShell ? &pViewShell->GetDocument() : nullptr ), rCellAddress, nIndex ), + mpViewShell( pViewShell ) +{ + if (mpViewShell) + mpViewShell->AddAccessibilityObject(*this); +} + +ScAccessiblePreviewCell::~ScAccessiblePreviewCell() +{ + if (!ScAccessibleContextBase::IsDefunc() && !rBHelper.bInDispose) + { + // increment refcount to prevent double call off dtor + osl_atomic_increment( &m_refCount ); + // call dispose to inform object which have a weak reference to this object + dispose(); + } +} + +void SAL_CALL ScAccessiblePreviewCell::disposing() +{ + SolarMutexGuard aGuard; + if (mpViewShell) + { + mpViewShell->RemoveAccessibilityObject(*this); + mpViewShell = nullptr; + } + + mpTextHelper.reset(); + + ScAccessibleCellBase::disposing(); +} + +void ScAccessiblePreviewCell::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if (rHint.GetId() == SfxHintId::ScAccVisAreaChanged) + { + if (mpTextHelper) + mpTextHelper->UpdateChildren(); + } + + ScAccessibleContextBase::Notify(rBC, rHint); +} + +//===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePreviewCell::getAccessibleAtPoint( const awt::Point& rPoint ) +{ + uno::Reference<XAccessible> xRet; + if (containsPoint(rPoint)) + { + SolarMutexGuard aGuard; + IsObjectValid(); + + if(!mpTextHelper) + CreateTextHelper(); + + xRet = mpTextHelper->GetAt(rPoint); + } + + return xRet; +} + +void SAL_CALL ScAccessiblePreviewCell::grabFocus() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleComponent> xAccessibleComponent(getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY); + if (xAccessibleComponent.is()) + xAccessibleComponent->grabFocus(); + } +} + +//===== XAccessibleContext ============================================== + +sal_Int64 SAL_CALL ScAccessiblePreviewCell::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (!mpTextHelper) + CreateTextHelper(); + return mpTextHelper->GetChildCount(); +} + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePreviewCell::getAccessibleChild(sal_Int64 nIndex) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (!mpTextHelper) + CreateTextHelper(); + return mpTextHelper->GetChild(nIndex); +} + +sal_Int64 SAL_CALL ScAccessiblePreviewCell::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + sal_Int64 nStateSet = 0; + if (IsDefunc(nParentStates)) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::MULTI_LINE; + if (IsOpaque()) + nStateSet |= AccessibleStateType::OPAQUE; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + nStateSet |= AccessibleStateType::TRANSIENT; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + // MANAGES_DESCENDANTS (for paragraphs) + nStateSet |= AccessibleStateType::MANAGES_DESCENDANTS; + } + return nStateSet; +} + +//===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessiblePreviewCell::getImplementationName() +{ + return "ScAccessiblePreviewCell"; +} + +uno::Sequence<OUString> SAL_CALL ScAccessiblePreviewCell::getSupportedServiceNames() +{ + const css::uno::Sequence<OUString> vals { "com.sun.star.table.AccessibleCellView" }; + return comphelper::concatSequences(ScAccessibleContextBase::getSupportedServiceNames(), vals); +} + +//===== XTypeProvider ======================================================= + +uno::Sequence<sal_Int8> SAL_CALL + ScAccessiblePreviewCell::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +//==== internal ========================================================= + +AbsoluteScreenPixelRectangle ScAccessiblePreviewCell::GetBoundingBoxOnScreen() const +{ + tools::Rectangle aCellRect; + if (mpViewShell) + { + mpViewShell->GetLocationData().GetCellPosition( maCellAddress, aCellRect ); + vcl::Window* pWindow = mpViewShell->GetWindow(); + if (pWindow) + { + AbsoluteScreenPixelRectangle aRect = pWindow->GetWindowExtentsAbsolute(); + aCellRect.Move(aRect.Left(), aRect.Top()); + } + } + return AbsoluteScreenPixelRectangle(aCellRect); +} + +tools::Rectangle ScAccessiblePreviewCell::GetBoundingBox() const +{ + tools::Rectangle aCellRect; + if (mpViewShell) + { + mpViewShell->GetLocationData().GetCellPosition( maCellAddress, aCellRect ); + uno::Reference<XAccessible> xAccParent = const_cast<ScAccessiblePreviewCell*>(this)->getAccessibleParent(); + if (xAccParent.is()) + { + uno::Reference<XAccessibleContext> xAccParentContext = xAccParent->getAccessibleContext(); + uno::Reference<XAccessibleComponent> xAccParentComp (xAccParentContext, uno::UNO_QUERY); + if (xAccParentComp.is()) + { + tools::Rectangle aParentRect (VCLRectangle(xAccParentComp->getBounds())); + aCellRect.Move(-aParentRect.Left(), -aParentRect.Top()); + } + } + } + return aCellRect; +} + +bool ScAccessiblePreviewCell::IsDefunc(sal_Int64 nParentStates) +{ + return ScAccessibleContextBase::IsDefunc() || (mpDoc == nullptr) || (mpViewShell == nullptr) || !getAccessibleParent().is() || + (nParentStates & AccessibleStateType::DEFUNC); +} + +bool ScAccessiblePreviewCell::IsEditable(sal_Int64 /* nParentStates */) +{ + return false; +} + +bool ScAccessiblePreviewCell::IsOpaque() const +{ + // test whether there is a background color + //! could be moved to ScAccessibleCellBase + + bool bOpaque(true); + if (mpDoc) + { + const SvxBrushItem* pItem = mpDoc->GetAttr(maCellAddress, ATTR_BACKGROUND); + if (pItem) + bOpaque = pItem->GetColor() != COL_TRANSPARENT; + } + return bOpaque; +} + +void ScAccessiblePreviewCell::CreateTextHelper() +{ + if (mpTextHelper) + return; + + mpTextHelper.reset( new ::accessibility::AccessibleTextHelper( + std::make_unique<ScAccessibilityEditSource>( + std::make_unique<ScAccessiblePreviewCellTextData>( + mpViewShell, maCellAddress))) ); + mpTextHelper->SetEventSource( this ); + + // paragraphs in preview are transient + mpTextHelper->SetAdditionalChildStates( AccessibleStateType::TRANSIENT ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessiblePreviewHeaderCell.cxx b/sc/source/ui/Accessibility/AccessiblePreviewHeaderCell.cxx new file mode 100644 index 0000000000..d772db890a --- /dev/null +++ b/sc/source/ui/Accessibility/AccessiblePreviewHeaderCell.cxx @@ -0,0 +1,403 @@ +/* -*- 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 <tools/gen.hxx> +#include <AccessibleText.hxx> +#include <editsrc.hxx> +#include <svx/AccessibleTextHelper.hxx> +#include <AccessiblePreviewHeaderCell.hxx> +#include <prevwsh.hxx> +#include <prevloc.hxx> +#include <strings.hxx> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <comphelper/sequence.hxx> + +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <svl/hint.hxx> +#include <toolkit/helper/convert.hxx> + +#ifdef indices +#undef indices +#endif + +#ifdef extents +#undef extents +#endif + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +//===== internal ============================================================ + +ScAccessiblePreviewHeaderCell::ScAccessiblePreviewHeaderCell( const css::uno::Reference<css::accessibility::XAccessible>& rxParent, + ScPreviewShell* pViewShell, + const ScAddress& rCellPos, bool bIsColHdr, bool bIsRowHdr, + sal_Int32 nIndex ) : + ScAccessibleContextBase( rxParent, AccessibleRole::TABLE_CELL ), + mpViewShell( pViewShell ), + mnIndex( nIndex ), + maCellPos( rCellPos ), + mbColumnHeader( bIsColHdr ), + mbRowHeader( bIsRowHdr ) +{ + if (mpViewShell) + mpViewShell->AddAccessibilityObject(*this); +} + +ScAccessiblePreviewHeaderCell::~ScAccessiblePreviewHeaderCell() +{ + if (mpViewShell) + mpViewShell->RemoveAccessibilityObject(*this); +} + +void SAL_CALL ScAccessiblePreviewHeaderCell::disposing() +{ + SolarMutexGuard aGuard; + if (mpViewShell) + { + mpViewShell->RemoveAccessibilityObject(*this); + mpViewShell = nullptr; + } + + mpTableInfo.reset(); + + ScAccessibleContextBase::disposing(); +} + +//===== SfxListener ===================================================== + +void ScAccessiblePreviewHeaderCell::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + const SfxHintId nId = rHint.GetId(); + if (nId == SfxHintId::ScAccVisAreaChanged) + { + if (mxTextHelper) + mxTextHelper->UpdateChildren(); + } + else if ( nId == SfxHintId::DataChanged ) + { + // column / row layout may change with any document change, + // so it must be invalidated + mpTableInfo.reset(); + } + + ScAccessibleContextBase::Notify(rBC, rHint); +} + +//===== XInterface ===================================================== + +uno::Any SAL_CALL ScAccessiblePreviewHeaderCell::queryInterface( uno::Type const & rType ) +{ + uno::Any aAny (ScAccessiblePreviewHeaderCellImpl::queryInterface(rType)); + return aAny.hasValue() ? aAny : ScAccessibleContextBase::queryInterface(rType); +} + +void SAL_CALL ScAccessiblePreviewHeaderCell::acquire() + noexcept +{ + ScAccessibleContextBase::acquire(); +} + +void SAL_CALL ScAccessiblePreviewHeaderCell::release() + noexcept +{ + ScAccessibleContextBase::release(); +} + +//===== XAccessibleValue ================================================ + +uno::Any SAL_CALL ScAccessiblePreviewHeaderCell::getCurrentValue() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + double fValue(0.0); + if (mbColumnHeader) + fValue = maCellPos.Col(); + else + fValue = maCellPos.Row(); + + return uno::Any(fValue); +} + +sal_Bool SAL_CALL ScAccessiblePreviewHeaderCell::setCurrentValue( const uno::Any& /* aNumber */ ) +{ + // it is not possible to set a value + return false; +} + +uno::Any SAL_CALL ScAccessiblePreviewHeaderCell::getMaximumValue() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + double fValue(0.0); + ScDocument& rDoc = mpViewShell->GetDocument(); + if (mbColumnHeader) + fValue = rDoc.MaxCol(); + else + fValue = rDoc.MaxRow(); + return uno::Any(fValue); +} + +uno::Any SAL_CALL ScAccessiblePreviewHeaderCell::getMinimumValue() +{ + return uno::Any(0.0); +} + +uno::Any SAL_CALL ScAccessiblePreviewHeaderCell::getMinimumIncrement() +{ + // value can't be changed, s. 'setCurrentValue' + return uno::Any(); +} + +//===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePreviewHeaderCell::getAccessibleAtPoint( const awt::Point& rPoint ) +{ + uno::Reference<XAccessible> xRet; + if (containsPoint(rPoint)) + { + SolarMutexGuard aGuard; + IsObjectValid(); + + if(!mxTextHelper) + CreateTextHelper(); + + xRet = mxTextHelper->GetAt(rPoint); + } + + return xRet; +} + +void SAL_CALL ScAccessiblePreviewHeaderCell::grabFocus() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleComponent> xAccessibleComponent(getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY); + if (xAccessibleComponent.is()) + xAccessibleComponent->grabFocus(); + } +} + +//===== XAccessibleContext ============================================== + +sal_Int64 SAL_CALL ScAccessiblePreviewHeaderCell::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (!mxTextHelper) + CreateTextHelper(); + return mxTextHelper->GetChildCount(); +} + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePreviewHeaderCell::getAccessibleChild(sal_Int64 nIndex) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (!mxTextHelper) + CreateTextHelper(); + return mxTextHelper->GetChild(nIndex); +} + +sal_Int64 SAL_CALL ScAccessiblePreviewHeaderCell::getAccessibleIndexInParent() +{ + return mnIndex; +} + +sal_Int64 SAL_CALL ScAccessiblePreviewHeaderCell::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + sal_Int64 nStateSet = 0; + if (IsDefunc(nParentStates)) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::MULTI_LINE; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + nStateSet |= AccessibleStateType::TRANSIENT; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + } + return nStateSet; +} + +//===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessiblePreviewHeaderCell::getImplementationName() +{ + return "ScAccessiblePreviewHeaderCell"; +} + +uno::Sequence<OUString> SAL_CALL ScAccessiblePreviewHeaderCell::getSupportedServiceNames() +{ + const css::uno::Sequence<OUString> vals { "com.sun.star.table.AccessibleCellView" }; + return comphelper::concatSequences(ScAccessibleContextBase::getSupportedServiceNames(), vals); +} + +//===== XTypeProvider ======================================================= + +uno::Sequence< uno::Type > SAL_CALL ScAccessiblePreviewHeaderCell::getTypes() +{ + return comphelper::concatSequences(ScAccessiblePreviewHeaderCellImpl::getTypes(), ScAccessibleContextBase::getTypes()); +} + +uno::Sequence<sal_Int8> SAL_CALL + ScAccessiblePreviewHeaderCell::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +//==== internal ========================================================= + +AbsoluteScreenPixelRectangle ScAccessiblePreviewHeaderCell::GetBoundingBoxOnScreen() const +{ + tools::Rectangle aCellRect; + + FillTableInfo(); + + if (mpTableInfo) + { + const ScPreviewColRowInfo& rColInfo = mpTableInfo->GetColInfo()[maCellPos.Col()]; + const ScPreviewColRowInfo& rRowInfo = mpTableInfo->GetRowInfo()[maCellPos.Row()]; + + aCellRect = tools::Rectangle( rColInfo.nPixelStart, rRowInfo.nPixelStart, rColInfo.nPixelEnd, rRowInfo.nPixelEnd ); + } + + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindow(); + if (pWindow) + { + AbsoluteScreenPixelRectangle aRect = pWindow->GetWindowExtentsAbsolute(); + aCellRect.Move(aRect.Left(), aRect.Top()); + } + } + return AbsoluteScreenPixelRectangle(aCellRect); +} + +tools::Rectangle ScAccessiblePreviewHeaderCell::GetBoundingBox() const +{ + FillTableInfo(); + + if (mpTableInfo) + { + const ScPreviewColRowInfo& rColInfo = mpTableInfo->GetColInfo()[maCellPos.Col()]; + const ScPreviewColRowInfo& rRowInfo = mpTableInfo->GetRowInfo()[maCellPos.Row()]; + + tools::Rectangle aCellRect( rColInfo.nPixelStart, rRowInfo.nPixelStart, rColInfo.nPixelEnd, rRowInfo.nPixelEnd ); + uno::Reference<XAccessible> xAccParent = const_cast<ScAccessiblePreviewHeaderCell*>(this)->getAccessibleParent(); + if (xAccParent.is()) + { + uno::Reference<XAccessibleContext> xAccParentContext = xAccParent->getAccessibleContext(); + uno::Reference<XAccessibleComponent> xAccParentComp (xAccParentContext, uno::UNO_QUERY); + if (xAccParentComp.is()) + { + tools::Rectangle aParentRect (VCLRectangle(xAccParentComp->getBounds())); + aCellRect.Move(-aParentRect.Left(), -aParentRect.Top()); + } + } + return aCellRect; + } + return tools::Rectangle(); +} + +OUString ScAccessiblePreviewHeaderCell::createAccessibleDescription() +{ + return STR_ACC_HEADERCELL_DESCR; +} + +OUString ScAccessiblePreviewHeaderCell::createAccessibleName() +{ + OUString sName = STR_ACC_HEADERCELL_NAME; + + if ( mbColumnHeader ) + { + if ( mbRowHeader ) + { + //! name for corner cell? + +// sName = "Column/Row Header"; + } + else + { + // name of column header + sName += ScColToAlpha( maCellPos.Col() ); + } + } + else + { + // name of row header + sName += OUString::number( maCellPos.Row() + 1 ); + } + + return sName; +} + +bool ScAccessiblePreviewHeaderCell::IsDefunc( sal_Int64 nParentStates ) +{ + return ScAccessibleContextBase::IsDefunc() || (mpViewShell == nullptr) || !getAccessibleParent().is() || + (nParentStates & AccessibleStateType::DEFUNC); +} + +void ScAccessiblePreviewHeaderCell::CreateTextHelper() +{ + if (!mxTextHelper) + { + mxTextHelper.reset( new ::accessibility::AccessibleTextHelper( + std::make_unique<ScAccessibilityEditSource>( + std::make_unique<ScAccessiblePreviewHeaderCellTextData>( + mpViewShell, getAccessibleName(), maCellPos, + mbColumnHeader, mbRowHeader))) ); + mxTextHelper->SetEventSource(this); + } +} + +void ScAccessiblePreviewHeaderCell::FillTableInfo() const +{ + if ( mpViewShell && !mpTableInfo ) + { + Size aOutputSize; + vcl::Window* pWindow = mpViewShell->GetWindow(); + if ( pWindow ) + aOutputSize = pWindow->GetOutputSizePixel(); + tools::Rectangle aVisRect( Point(), aOutputSize ); + + mpTableInfo.reset( new ScPreviewTableInfo ); + mpViewShell->GetLocationData().GetTableInfo( aVisRect, *mpTableInfo ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessiblePreviewTable.cxx b/sc/source/ui/Accessibility/AccessiblePreviewTable.cxx new file mode 100644 index 0000000000..925ad1075c --- /dev/null +++ b/sc/source/ui/Accessibility/AccessiblePreviewTable.cxx @@ -0,0 +1,640 @@ +/* -*- 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 <scitems.hxx> +#include <AccessiblePreviewTable.hxx> +#include <AccessiblePreviewCell.hxx> +#include <AccessiblePreviewHeaderCell.hxx> +#include <prevwsh.hxx> +#include <prevloc.hxx> +#include <attrib.hxx> +#include <document.hxx> +#include <scresid.hxx> +#include <strings.hrc> +#include <strings.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 <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <svl/hint.hxx> +#include <comphelper/sequence.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +//===== internal ============================================================ + +ScAccessiblePreviewTable::ScAccessiblePreviewTable( const css::uno::Reference<css::accessibility::XAccessible>& rxParent, + ScPreviewShell* pViewShell, sal_Int32 nIndex ) : + ScAccessibleContextBase( rxParent, AccessibleRole::TABLE ), + mpViewShell( pViewShell ), + mnIndex( nIndex ) +{ + if (mpViewShell) + mpViewShell->AddAccessibilityObject(*this); +} + +ScAccessiblePreviewTable::~ScAccessiblePreviewTable() +{ + if (!ScAccessibleContextBase::IsDefunc() && !rBHelper.bInDispose) + { + // increment refcount to prevent double call off dtor + osl_atomic_increment( &m_refCount ); + dispose(); + } +} + +void SAL_CALL ScAccessiblePreviewTable::disposing() +{ + SolarMutexGuard aGuard; + if (mpViewShell) + { + mpViewShell->RemoveAccessibilityObject(*this); + mpViewShell = nullptr; + } + + mpTableInfo.reset(); + + ScAccessibleContextBase::disposing(); +} + +//===== SfxListener ===================================================== + +void ScAccessiblePreviewTable::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + const SfxHintId nId = rHint.GetId(); + if ( nId == SfxHintId::DataChanged ) + { + // column / row layout may change with any document change, + // so it must be invalidated + mpTableInfo.reset(); + } + else if (nId == SfxHintId::ScAccVisAreaChanged) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::VISIBLE_DATA_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + CommitChange(aEvent); + } + + ScAccessibleContextBase::Notify(rBC, rHint); +} + +//===== XInterface ===================================================== + +uno::Any SAL_CALL ScAccessiblePreviewTable::queryInterface( uno::Type const & rType ) +{ + uno::Any aAny (ScAccessiblePreviewTableImpl::queryInterface(rType)); + return aAny.hasValue() ? aAny : ScAccessibleContextBase::queryInterface(rType); +} + +void SAL_CALL ScAccessiblePreviewTable::acquire() + noexcept +{ + ScAccessibleContextBase::acquire(); +} + +void SAL_CALL ScAccessiblePreviewTable::release() + noexcept +{ + ScAccessibleContextBase::release(); +} + +//===== XAccessibleTable ================================================ + +sal_Int32 SAL_CALL ScAccessiblePreviewTable::getAccessibleRowCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + sal_Int32 nRet = 0; + if ( mpTableInfo ) + nRet = mpTableInfo->GetRows(); + return nRet; +} + +sal_Int32 SAL_CALL ScAccessiblePreviewTable::getAccessibleColumnCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + sal_Int32 nRet = 0; + if ( mpTableInfo ) + nRet = mpTableInfo->GetCols(); + return nRet; +} + +OUString SAL_CALL ScAccessiblePreviewTable::getAccessibleRowDescription( sal_Int32 nRow ) +{ + SolarMutexGuard aGuard; + FillTableInfo(); + if ( nRow < 0 || (mpTableInfo && nRow >= mpTableInfo->GetRows()) ) + throw lang::IndexOutOfBoundsException(); + + return OUString(); +} + +OUString SAL_CALL ScAccessiblePreviewTable::getAccessibleColumnDescription( sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + FillTableInfo(); + if ( nColumn < 0 || (mpTableInfo && nColumn >= mpTableInfo->GetCols()) ) + throw lang::IndexOutOfBoundsException(); + + return OUString(); +} + +sal_Int32 SAL_CALL ScAccessiblePreviewTable::getAccessibleRowExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + sal_Int32 nRows = 1; + if ( !mpViewShell || !mpTableInfo || nColumn < 0 || nRow < 0 || + nColumn >= mpTableInfo->GetCols() || nRow >= mpTableInfo->GetRows() ) + throw lang::IndexOutOfBoundsException(); + + const ScPreviewColRowInfo& rColInfo = mpTableInfo->GetColInfo()[nColumn]; + const ScPreviewColRowInfo& rRowInfo = mpTableInfo->GetRowInfo()[nRow]; + + if ( rColInfo.bIsHeader || rRowInfo.bIsHeader ) + { + // header cells only span a single cell + } + else + { + ScDocument& rDoc = mpViewShell->GetDocument(); + const ScMergeAttr* pItem = rDoc.GetAttr( + static_cast<SCCOL>(rColInfo.nDocIndex), static_cast<SCROW>(rRowInfo.nDocIndex), mpTableInfo->GetTab(), ATTR_MERGE ); + if ( pItem && pItem->GetRowMerge() > 0 ) + nRows = pItem->GetRowMerge(); + } + + return nRows; +} + +sal_Int32 SAL_CALL ScAccessiblePreviewTable::getAccessibleColumnExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + sal_Int32 nColumns = 1; + if ( !mpViewShell || !mpTableInfo || nColumn < 0 || nRow < 0 || + nColumn >= mpTableInfo->GetCols() || nRow >= mpTableInfo->GetRows() ) + throw lang::IndexOutOfBoundsException(); + + const ScPreviewColRowInfo& rColInfo = mpTableInfo->GetColInfo()[nColumn]; + const ScPreviewColRowInfo& rRowInfo = mpTableInfo->GetRowInfo()[nRow]; + + if ( rColInfo.bIsHeader || rRowInfo.bIsHeader ) + { + // header cells only span a single cell + } + else + { + ScDocument& rDoc = mpViewShell->GetDocument(); + const ScMergeAttr* pItem = rDoc.GetAttr( + static_cast<SCCOL>(rColInfo.nDocIndex), static_cast<SCROW>(rRowInfo.nDocIndex), mpTableInfo->GetTab(), ATTR_MERGE ); + if ( pItem && pItem->GetColMerge() > 0 ) + nColumns = pItem->GetColMerge(); + } + + return nColumns; +} + +uno::Reference< XAccessibleTable > SAL_CALL ScAccessiblePreviewTable::getAccessibleRowHeaders() +{ + //! missing + return nullptr; +} + +uno::Reference< XAccessibleTable > SAL_CALL ScAccessiblePreviewTable::getAccessibleColumnHeaders() +{ + //! missing + return nullptr; +} + +uno::Sequence< sal_Int32 > SAL_CALL ScAccessiblePreviewTable::getSelectedAccessibleRows() +{ + // in the page preview, there is no selection + return {}; +} + +uno::Sequence< sal_Int32 > SAL_CALL ScAccessiblePreviewTable::getSelectedAccessibleColumns() +{ + // in the page preview, there is no selection + return {}; +} + +sal_Bool SAL_CALL ScAccessiblePreviewTable::isAccessibleRowSelected( sal_Int32 nRow ) +{ + // in the page preview, there is no selection + + SolarMutexGuard aGuard; + FillTableInfo(); + if ( nRow < 0 || (mpTableInfo && nRow >= mpTableInfo->GetRows()) ) + throw lang::IndexOutOfBoundsException(); + + return false; +} + +sal_Bool SAL_CALL ScAccessiblePreviewTable::isAccessibleColumnSelected( sal_Int32 nColumn ) +{ + // in the page preview, there is no selection + + SolarMutexGuard aGuard; + FillTableInfo(); + if ( nColumn < 0 || (mpTableInfo && nColumn >= mpTableInfo->GetCols()) ) + throw lang::IndexOutOfBoundsException(); + + return false; +} + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePreviewTable::getAccessibleCellAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + uno::Reference<XAccessible> xRet; + if ( mpTableInfo && nColumn >= 0 && nRow >= 0 && nColumn < mpTableInfo->GetCols() && nRow < mpTableInfo->GetRows() ) + { + // index iterates horizontally + tools::Long nNewIndex = nRow * mpTableInfo->GetCols() + nColumn; + + const ScPreviewColRowInfo& rColInfo = mpTableInfo->GetColInfo()[nColumn]; + const ScPreviewColRowInfo& rRowInfo = mpTableInfo->GetRowInfo()[nRow]; + + ScAddress aCellPos( static_cast<SCCOL>(rColInfo.nDocIndex), static_cast<SCROW>(rRowInfo.nDocIndex), mpTableInfo->GetTab() ); + if ( rColInfo.bIsHeader || rRowInfo.bIsHeader ) + { + const bool bRotatedColHeader = rRowInfo.bIsHeader; + const bool bRotatedRowHeader = rColInfo.bIsHeader; + rtl::Reference<ScAccessiblePreviewHeaderCell> pHeaderCell(new ScAccessiblePreviewHeaderCell(this, mpViewShell, aCellPos, + bRotatedColHeader, bRotatedRowHeader, nNewIndex)); + xRet = pHeaderCell.get(); + pHeaderCell->Init(); + } + else + { + rtl::Reference<ScAccessiblePreviewCell> pCell(new ScAccessiblePreviewCell( this, mpViewShell, aCellPos, nNewIndex )); + xRet = pCell.get(); + pCell->Init(); + } + } + + if ( !xRet.is() ) + throw lang::IndexOutOfBoundsException(); + + return xRet; +} + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePreviewTable::getAccessibleCaption() +{ + //! missing + return nullptr; +} + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePreviewTable::getAccessibleSummary() +{ + //! missing + return nullptr; +} + +sal_Bool SAL_CALL ScAccessiblePreviewTable::isAccessibleSelected( sal_Int32 nRow, sal_Int32 nColumn ) +{ + // in the page preview, there is no selection + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + if ( !mpTableInfo || nColumn < 0 || nRow < 0 || nColumn >= mpTableInfo->GetCols() || nRow >= mpTableInfo->GetRows() ) + throw lang::IndexOutOfBoundsException(); + + // index iterates horizontally + return false; +} + +sal_Int64 SAL_CALL ScAccessiblePreviewTable::getAccessibleIndex( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + if ( !mpTableInfo || nColumn < 0 || nRow < 0 || nColumn >= mpTableInfo->GetCols() || nRow >= mpTableInfo->GetRows() ) + throw lang::IndexOutOfBoundsException(); + + // index iterates horizontally + sal_Int64 nRet = static_cast<sal_Int64>(nRow) * static_cast<sal_Int64>(mpTableInfo->GetCols()) + nColumn; + return nRet; +} + +sal_Int32 SAL_CALL ScAccessiblePreviewTable::getAccessibleRow( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + if ( !mpTableInfo || nChildIndex < 0 || nChildIndex >= static_cast<sal_Int64>(mpTableInfo->GetRows()) * mpTableInfo->GetCols() ) + throw lang::IndexOutOfBoundsException(); + + sal_Int32 nRow = nChildIndex / mpTableInfo->GetCols(); + return nRow; +} + +sal_Int32 SAL_CALL ScAccessiblePreviewTable::getAccessibleColumn( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + if ( !mpTableInfo || nChildIndex < 0 || nChildIndex >= static_cast<sal_Int64>(mpTableInfo->GetRows()) * mpTableInfo->GetCols() ) + throw lang::IndexOutOfBoundsException(); + + sal_Int32 nCol = nChildIndex % static_cast<sal_Int32>(mpTableInfo->GetCols()); + return nCol; +} + +//===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePreviewTable::getAccessibleAtPoint( const awt::Point& aPoint ) +{ + uno::Reference<XAccessible> xRet; + if (containsPoint(aPoint)) + { + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + if ( mpTableInfo ) + { + SCCOL nCols = mpTableInfo->GetCols(); + SCROW nRows = mpTableInfo->GetRows(); + const ScPreviewColRowInfo* pColInfo = mpTableInfo->GetColInfo(); + const ScPreviewColRowInfo* pRowInfo = mpTableInfo->GetRowInfo(); + + tools::Rectangle aScreenRect(GetBoundingBox()); + + awt::Point aMovedPoint = aPoint; + aMovedPoint.X += aScreenRect.Left(); + aMovedPoint.Y += aScreenRect.Top(); + + if ( nCols > 0 && nRows > 0 && aMovedPoint.X >= pColInfo[0].nPixelStart && aMovedPoint.Y >= pRowInfo[0].nPixelStart ) + { + SCCOL nColIndex = 0; + while ( nColIndex < nCols && aMovedPoint.X > pColInfo[nColIndex].nPixelEnd ) + ++nColIndex; + SCROW nRowIndex = 0; + while ( nRowIndex < nRows && aMovedPoint.Y > pRowInfo[nRowIndex].nPixelEnd ) + ++nRowIndex; + if ( nColIndex < nCols && nRowIndex < nRows ) + { + try + { + xRet = getAccessibleCellAt( nRowIndex, nColIndex ); + } + catch (uno::Exception&) + { + } + } + } + } + } + + return xRet; +} + +void SAL_CALL ScAccessiblePreviewTable::grabFocus() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleComponent> xAccessibleComponent(getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY); + if (xAccessibleComponent.is()) + xAccessibleComponent->grabFocus(); + } +} + +//===== XAccessibleContext ============================================== + +sal_Int64 SAL_CALL ScAccessiblePreviewTable::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + tools::Long nRet = 0; + if ( mpTableInfo ) + nRet = static_cast<sal_Int64>(mpTableInfo->GetCols()) * mpTableInfo->GetRows(); + return nRet; +} + +uno::Reference< XAccessible > SAL_CALL ScAccessiblePreviewTable::getAccessibleChild( sal_Int64 nIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + FillTableInfo(); + + uno::Reference<XAccessible> xRet; + if ( mpTableInfo ) + { + sal_Int32 nColumns = mpTableInfo->GetCols(); + if ( nColumns > 0 ) + { + // nCol, nRow are within the visible table, not the document + sal_Int32 nCol = nIndex % nColumns; + sal_Int64 nRow = nIndex / nColumns; + assert(nRow <= std::numeric_limits<sal_Int32>::max()); + xRet = getAccessibleCellAt( nRow, nCol ); + } + } + + if ( !xRet.is() ) + throw lang::IndexOutOfBoundsException(); + + return xRet; +} + +sal_Int64 SAL_CALL ScAccessiblePreviewTable::getAccessibleIndexInParent() +{ + return mnIndex; +} + +sal_Int64 SAL_CALL ScAccessiblePreviewTable::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + sal_Int64 nStateSet = 0; + if (IsDefunc(nParentStates)) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + nStateSet |= AccessibleStateType::MANAGES_DESCENDANTS; + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::OPAQUE; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + } + return nStateSet; +} + +//===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessiblePreviewTable::getImplementationName() +{ + return "ScAccessiblePreviewTable"; +} + +uno::Sequence<OUString> SAL_CALL ScAccessiblePreviewTable::getSupportedServiceNames() +{ + uno::Sequence< OUString > aSequence = ScAccessibleContextBase::getSupportedServiceNames(); + sal_Int32 nOldSize(aSequence.getLength()); + aSequence.realloc(nOldSize + 1); + + aSequence.getArray()[nOldSize] = "com.sun.star.table.AccessibleTableView"; + + return aSequence; +} + +//===== XTypeProvider =================================================== + +uno::Sequence< uno::Type > SAL_CALL ScAccessiblePreviewTable::getTypes() +{ + return comphelper::concatSequences(ScAccessiblePreviewTableImpl::getTypes(), ScAccessibleContextBase::getTypes()); +} + +uno::Sequence<sal_Int8> SAL_CALL ScAccessiblePreviewTable::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +//==== internal ========================================================= + +OUString ScAccessiblePreviewTable::createAccessibleDescription() +{ + return STR_ACC_TABLE_DESCR; +} + +OUString ScAccessiblePreviewTable::createAccessibleName() +{ + OUString sName(ScResId(STR_ACC_TABLE_NAME)); + + if (mpViewShell) + { + FillTableInfo(); + + if ( mpTableInfo ) + { + OUString sCoreName; + if (mpViewShell->GetDocument().GetName( mpTableInfo->GetTab(), sCoreName )) + sName = sName.replaceFirst("%1", sCoreName); + } + } + + return sName; +} + +AbsoluteScreenPixelRectangle ScAccessiblePreviewTable::GetBoundingBoxOnScreen() const +{ + tools::Rectangle aCellRect(GetBoundingBox()); + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindow(); + if (pWindow) + { + AbsoluteScreenPixelRectangle aRect = pWindow->GetWindowExtentsAbsolute(); + aCellRect.Move(aRect.Left(), aRect.Top()); + } + } + return AbsoluteScreenPixelRectangle(aCellRect); +} + +tools::Rectangle ScAccessiblePreviewTable::GetBoundingBox() const +{ + FillTableInfo(); + + tools::Rectangle aRect; + if ( mpTableInfo ) + { + SCCOL nColumns = mpTableInfo->GetCols(); + SCROW nRows = mpTableInfo->GetRows(); + if ( nColumns > 0 && nRows > 0 ) + { + const ScPreviewColRowInfo* pColInfo = mpTableInfo->GetColInfo(); + const ScPreviewColRowInfo* pRowInfo = mpTableInfo->GetRowInfo(); + + aRect = tools::Rectangle( pColInfo[0].nPixelStart, + pRowInfo[0].nPixelStart, + pColInfo[nColumns-1].nPixelEnd, + pRowInfo[nRows-1].nPixelEnd ); + } + } + return aRect; +} + +bool ScAccessiblePreviewTable::IsDefunc( sal_Int64 nParentStates ) +{ + return ScAccessibleContextBase::IsDefunc() || (mpViewShell == nullptr) || !getAccessibleParent().is() || + (nParentStates & AccessibleStateType::DEFUNC); +} + +void ScAccessiblePreviewTable::FillTableInfo() const +{ + if ( mpViewShell && !mpTableInfo ) + { + Size aOutputSize; + vcl::Window* pWindow = mpViewShell->GetWindow(); + if ( pWindow ) + aOutputSize = pWindow->GetOutputSizePixel(); + tools::Rectangle aVisRect( Point(), aOutputSize ); + + mpTableInfo.reset( new ScPreviewTableInfo ); + mpViewShell->GetLocationData().GetTableInfo( aVisRect, *mpTableInfo ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleSpreadsheet.cxx b/sc/source/ui/Accessibility/AccessibleSpreadsheet.cxx new file mode 100644 index 0000000000..7aaa7237cc --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleSpreadsheet.cxx @@ -0,0 +1,1669 @@ +/* -*- 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 <AccessibleSpreadsheet.hxx> +#include <AccessibleCell.hxx> +#include <AccessibleDocument.hxx> +#include <tabvwsh.hxx> +#include <document.hxx> +#include <hints.hxx> +#include <scmod.hxx> +#include <markdata.hxx> +#include <gridwin.hxx> + +#include <o3tl/safeint.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <sal/log.hxx> +#include <tools/gen.hxx> +#include <svtools/colorcfg.hxx> +#include <vcl/svapp.hxx> +#include <scresid.hxx> +#include <strings.hrc> + +#include <algorithm> +#include <cstdlib> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +static bool CompMinCol(const std::pair<sal_uInt16,sal_uInt16> & pc1,const std::pair<sal_uInt16,sal_uInt16> &pc2) +{ + return pc1.first < pc2.first; +} + +ScMyAddress ScAccessibleSpreadsheet::CalcScAddressFromRangeList(ScRangeList *pMarkedRanges,sal_Int32 nSelectedChildIndex) +{ + if (pMarkedRanges->size() <= 1) + { + ScRange const & rRange = pMarkedRanges->front(); + // MT IA2: Not used. + // const int nRowNum = rRange.aEnd.Row() - rRange.aStart.Row() + 1; + const int nColNum = rRange.aEnd.Col() - rRange.aStart.Col() + 1; + const int nCurCol = nSelectedChildIndex % nColNum; + const int nCurRow = (nSelectedChildIndex - nCurCol)/nColNum; + return ScMyAddress(static_cast<SCCOL>(rRange.aStart.Col() + nCurCol), rRange.aStart.Row() + nCurRow, maActiveCell.Tab()); + } + else + { + ScDocument* pDoc= GetDocument(mpViewShell); + sal_Int32 nMinRow = pDoc->MaxRow(); + sal_Int32 nMaxRow = 0; + std::vector<ScRange> aRanges; + size_t nSize = pMarkedRanges->size(); + for (size_t i = 0; i < nSize; ++i) + { + ScRange const & rRange = (*pMarkedRanges)[i]; + if (rRange.aStart.Tab() != rRange.aEnd.Tab()) + { + if ((maActiveCell.Tab() >= rRange.aStart.Tab()) || + maActiveCell.Tab() <= rRange.aEnd.Tab()) + { + aRanges.push_back(rRange); + nMinRow = std::min(rRange.aStart.Row(),nMinRow); + nMaxRow = std::max(rRange.aEnd.Row(),nMaxRow); + } + else + SAL_WARN("sc", "Range of wrong table"); + } + else if(rRange.aStart.Tab() == maActiveCell.Tab()) + { + aRanges.push_back(rRange); + nMinRow = std::min(rRange.aStart.Row(),nMinRow); + nMaxRow = std::max(rRange.aEnd.Row(),nMaxRow); + } + else + SAL_WARN("sc", "Range of wrong table"); + } + int nCurrentIndex = 0 ; + for(sal_Int32 row = nMinRow ; row <= nMaxRow ; ++row) + { + std::vector<std::pair<SCCOL, SCCOL>> aVecCol; + for (ScRange const & r : aRanges) + { + if ( row >= r.aStart.Row() && row <= r.aEnd.Row()) + { + aVecCol.emplace_back(r.aStart.Col(), r.aEnd.Col()); + } + } + std::sort(aVecCol.begin(), aVecCol.end(), CompMinCol); + for (const std::pair<SCCOL, SCCOL> &pairCol : aVecCol) + { + SCCOL nCol = pairCol.second - pairCol.first + 1; + if (nCol + nCurrentIndex > nSelectedChildIndex) + { + return ScMyAddress(static_cast<SCCOL>(pairCol.first + nSelectedChildIndex - nCurrentIndex), row, maActiveCell.Tab()); + } + nCurrentIndex += nCol; + } + } + } + return ScMyAddress(0,0,maActiveCell.Tab()); +} + +bool ScAccessibleSpreadsheet::CalcScRangeDifferenceMax(const ScRange & rSrc, const ScRange & rDest, int nMax, + std::vector<ScMyAddress> &vecRet, int &nSize) +{ + //Src Must be :Src > Dest + if (rDest.Contains(rSrc)) + {//Here is Src In Dest,Src <= Dest + return false; + } + if (!rDest.Intersects(rSrc)) + { + int nCellCount = sal_uInt32(rDest.aEnd.Col() - rDest.aStart.Col() + 1) + * sal_uInt32(rDest.aEnd.Row() - rDest.aStart.Row() + 1) + * sal_uInt32(rDest.aEnd.Tab() - rDest.aStart.Tab() + 1); + if (nCellCount + nSize > nMax) + { + return true; + } + else if(nCellCount > 0) + { + for (sal_Int32 row = rDest.aStart.Row(); row <= rDest.aEnd.Row();++row) + { + for (sal_uInt16 col = rDest.aStart.Col(); col <= rDest.aEnd.Col();++col) + { + vecRet.emplace_back(col,row,rDest.aStart.Tab()); + } + } + } + return false; + } + sal_Int32 nMinRow = rSrc.aStart.Row(); + sal_Int32 nMaxRow = rSrc.aEnd.Row(); + for (; nMinRow <= nMaxRow ; ++nMinRow,--nMaxRow) + { + for (sal_uInt16 col = rSrc.aStart.Col(); col <= rSrc.aEnd.Col();++col) + { + if (nSize > nMax) + { + return true; + } + ScMyAddress cell(col,nMinRow,rSrc.aStart.Tab()); + if(!rDest.Contains(cell)) + {//In Src ,Not In Dest + vecRet.push_back(cell); + ++nSize; + } + } + if (nMinRow != nMaxRow) + { + for (sal_uInt16 col = rSrc.aStart.Col(); col <= rSrc.aEnd.Col();++col) + { + if (nSize > nMax) + { + return true; + } + ScMyAddress cell(col,nMaxRow,rSrc.aStart.Tab()); + if(!rDest.Contains(cell)) + {//In Src ,Not In Dest + vecRet.push_back(cell); + ++nSize; + } + } + } + } + return false; +} + +//In Src , Not in Dest +bool ScAccessibleSpreadsheet::CalcScRangeListDifferenceMax(ScRangeList *pSrc, ScRangeList *pDest, + int nMax, std::vector<ScMyAddress> &vecRet) +{ + if (pSrc == nullptr || pDest == nullptr) + { + return false; + } + int nSize =0; + if (pDest->GetCellCount() == 0)//if the Dest Rang List is empty + { + if (pSrc->GetCellCount() > o3tl::make_unsigned(nMax))//if the Src Cell count is greater than nMax + { + return true; + } + //now the cell count is less than nMax + vecRet.reserve(10); + size_t nSrcSize = pSrc->size(); + for (size_t i = 0; i < nSrcSize; ++i) + { + ScRange const & rRange = (*pSrc)[i]; + for (sal_Int32 row = rRange.aStart.Row(); row <= rRange.aEnd.Row();++row) + { + for (sal_uInt16 col = rRange.aStart.Col(); col <= rRange.aEnd.Col();++col) + { + vecRet.emplace_back(col,row, rRange.aStart.Tab()); + } + } + } + return false; + } + //the Dest Rang List is not empty + vecRet.reserve(10); + size_t nSizeSrc = pSrc->size(); + for (size_t i = 0; i < nSizeSrc; ++i) + { + ScRange const & rRange = (*pSrc)[i]; + size_t nSizeDest = pDest->size(); + for (size_t j = 0; j < nSizeDest; ++j) + { + ScRange const & rRangeDest = (*pDest)[j]; + if (CalcScRangeDifferenceMax(rRange,rRangeDest,nMax,vecRet,nSize)) + { + return true; + } + } + } + return false; +} + +//===== internal ============================================================ + +// FIXME: really unclear why we have an ScAccessibleTableBase with +// only this single sub-class +ScAccessibleSpreadsheet::ScAccessibleSpreadsheet( + ScAccessibleDocument* pAccDoc, + ScTabViewShell* pViewShell, + SCTAB nTab, + ScSplitPos eSplitPos) + : + ScAccessibleTableBase( pAccDoc, GetDocument(pViewShell), ScRange( 0, 0, nTab, GetDocument(pViewShell)->MaxCol(), GetDocument(pViewShell)->MaxRow(), nTab)), + mbIsSpreadsheet( true ), + m_bFormulaMode( false ), + m_bFormulaLastMode( false ), + m_nMinX(0),m_nMaxX(0),m_nMinY(0),m_nMaxY(0) +{ + ConstructScAccessibleSpreadsheet( pAccDoc, pViewShell, nTab, eSplitPos ); +} + +ScAccessibleSpreadsheet::ScAccessibleSpreadsheet( + ScAccessibleSpreadsheet& rParent, const ScRange& rRange ) : + ScAccessibleTableBase( rParent.mpAccDoc, rParent.mpDoc, rRange), + mbIsSpreadsheet( false ), + m_bFormulaMode( false ), + m_bFormulaLastMode( false ), + m_nMinX(0),m_nMaxX(0),m_nMinY(0),m_nMaxY(0) +{ + ConstructScAccessibleSpreadsheet( rParent.mpAccDoc, rParent.mpViewShell, rParent.mnTab, rParent.meSplitPos ); +} + +ScAccessibleSpreadsheet::~ScAccessibleSpreadsheet() +{ + mpMarkedRanges.reset(); + if (mpViewShell) + mpViewShell->RemoveAccessibilityObject(*this); +} + +void ScAccessibleSpreadsheet::ConstructScAccessibleSpreadsheet( + ScAccessibleDocument* pAccDoc, + ScTabViewShell* pViewShell, + SCTAB nTab, + ScSplitPos eSplitPos) +{ + mpViewShell = pViewShell; + mpMarkedRanges = nullptr; + mpAccDoc = pAccDoc; + mpAccCell.clear(); + meSplitPos = eSplitPos; + mnTab = nTab; + mbDelIns = false; + mbIsFocusSend = false; + if (!mpViewShell) + return; + + mpViewShell->AddAccessibilityObject(*this); + + const ScViewData& rViewData = mpViewShell->GetViewData(); + maActiveCell = rViewData.GetCurPos(); + mpAccCell = GetAccessibleCellAt(maActiveCell.Row(), maActiveCell.Col()); + ScDocument* pScDoc= GetDocument(mpViewShell); + if (pScDoc) + { + pScDoc->GetName( maActiveCell.Tab(), m_strOldTabName ); + } +} + +void SAL_CALL ScAccessibleSpreadsheet::disposing() +{ + SolarMutexGuard aGuard; + if (mpViewShell) + { + mpViewShell->RemoveAccessibilityObject(*this); + mpViewShell = nullptr; + } + mpAccCell.clear(); + + ScAccessibleTableBase::disposing(); +} + +void ScAccessibleSpreadsheet::CompleteSelectionChanged(bool bNewState) +{ + if (IsFormulaMode()) + { + return ; + } + mpMarkedRanges.reset(); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::STATE_CHANGED; + if (bNewState) + aEvent.NewValue <<= AccessibleStateType::SELECTED; + else + aEvent.OldValue <<= AccessibleStateType::SELECTED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + + CommitChange(aEvent); +} + +void ScAccessibleSpreadsheet::LostFocus() +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::ACTIVE_DESCENDANT_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + aEvent.OldValue <<= uno::Reference<XAccessible>(mpAccCell); + + CommitChange(aEvent); + + CommitFocusLost(); +} + +void ScAccessibleSpreadsheet::GotFocus() +{ + CommitFocusGained(); + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::ACTIVE_DESCENDANT_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + uno::Reference< XAccessible > xNew; + if (IsFormulaMode()) + { + if (!m_pAccFormulaCell.is() || !m_bFormulaLastMode) + { + ScAddress aFormulaAddr; + if(!GetFormulaCurrentFocusCell(aFormulaAddr)) + { + return; + } + m_pAccFormulaCell = GetAccessibleCellAt(aFormulaAddr.Row(),aFormulaAddr.Col()); + } + xNew = m_pAccFormulaCell.get(); + } + else + { + if(mpAccCell->GetCellAddress() == maActiveCell) + { + xNew = mpAccCell.get(); + } + else + { + CommitFocusCell(maActiveCell); + return ; + } + } + aEvent.NewValue <<= xNew; + + CommitChange(aEvent); +} + +void ScAccessibleSpreadsheet::BoundingBoxChanged() +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::BOUNDRECT_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + + CommitChange(aEvent); +} + +void ScAccessibleSpreadsheet::VisAreaChanged() +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::VISIBLE_DATA_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + + CommitChange(aEvent); +} + + //===== SfxListener ===================================================== + +void ScAccessibleSpreadsheet::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if ( auto pRefHint = dynamic_cast<const ScUpdateRefHint*>(&rHint) ) + { + if (pRefHint->GetMode() == URM_INSDEL && pRefHint->GetDz() == 0) //test whether table is inserted or deleted + { + if (((pRefHint->GetRange().aStart.Col() == maRange.aStart.Col()) && + (pRefHint->GetRange().aEnd.Col() == maRange.aEnd.Col())) || + ((pRefHint->GetRange().aStart.Row() == maRange.aStart.Row()) && + (pRefHint->GetRange().aEnd.Row() == maRange.aEnd.Row()))) + { + // ignore next SfxHintId::ScDataChanged notification + mbDelIns = true; + + SCROW nFirstRow = -1; + SCROW nLastRow = -1; + SCCOL nFirstCol = -1; + SCCOL nLastCol = -1; + + sal_Int16 nId(0); + SCCOL nX(pRefHint->GetDx()); + SCROW nY(pRefHint->GetDy()); + ScRange aRange(pRefHint->GetRange()); + if ((nX < 0) || (nY < 0)) + { + assert(!((nX < 0) && (nY < 0)) && "should not be possible to remove row and column at the same time"); + + // Range in the update hint is the range after the removed rows/columns; + // calculate indices for the removed ones from that + if (nX < 0) + { + nId = AccessibleTableModelChangeType::COLUMNS_REMOVED; + nFirstCol = aRange.aStart.Col() + nX; + nLastCol = aRange.aStart.Col() - 1; + } + else + { + nId = AccessibleTableModelChangeType::ROWS_REMOVED; + nFirstRow = aRange.aStart.Row() + nY; + nLastRow = aRange.aStart.Row() - 1; + } + } + else if ((nX > 0) || (nY > 0)) + { + assert(!((nX > 0) && (nY > 0)) && "should not be possible to add row and column at the same time"); + + // Range in the update hint is from first inserted row/column to last one in spreadsheet; + // calculate indices for the inserted ones from that + if (nX > 0) + { + nId = AccessibleTableModelChangeType::COLUMNS_INSERTED; + nFirstCol = aRange.aStart.Col(); + nLastCol = aRange.aStart.Col() + nX - 1; + } + else + { + nId = AccessibleTableModelChangeType::ROWS_INSERTED; + nFirstRow = aRange.aStart.Row(); + nLastRow = aRange.aStart.Row() + nY -1; + } + } + else + { + assert(false && "is it a deletion or an insertion?"); + } + + CommitTableModelChange(nFirstRow, nFirstCol, nLastRow, nLastCol, nId); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::ACTIVE_DESCENDANT_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + aEvent.NewValue <<= uno::Reference<XAccessible>(mpAccCell); + + CommitChange(aEvent); + } + } + } + else + { + if (rHint.GetId() == SfxHintId::ScAccCursorChanged) + { + if (mpViewShell) + { + ScViewData& rViewData = mpViewShell->GetViewData(); + + m_bFormulaMode = rViewData.IsRefMode() || SC_MOD()->IsFormulaMode(); + if ( m_bFormulaMode ) + { + NotifyRefMode(); + m_bFormulaLastMode = true; + return; + } + if (m_bFormulaLastMode) + {//Last Notify Mode Is Formula Mode. + m_vecFormulaLastMyAddr.clear(); + RemoveFormulaSelection(true); + m_pAccFormulaCell.clear(); + //Remove All Selection + } + m_bFormulaLastMode = m_bFormulaMode; + + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference< XAccessible >(this); + ScAddress aNewCell = rViewData.GetCurPos(); + if(aNewCell.Tab() != maActiveCell.Tab()) + { + aEvent.EventId = AccessibleEventId::PAGE_CHANGED; + auto pAccParent = getAccessibleParent(); + ScAccessibleDocument *pAccDoc = + static_cast<ScAccessibleDocument*>(pAccParent.get()); + if(pAccDoc) + { + pAccDoc->CommitChange(aEvent); + } + } + bool bNewPosCell = (aNewCell != maActiveCell) || mpViewShell->GetForceFocusOnCurCell(); // #i123629# + bool bNewPosCellFocus=false; + if ( bNewPosCell && IsFocused() && aNewCell.Tab() == maActiveCell.Tab() ) + {//single Focus + bNewPosCellFocus=true; + } + ScMarkData &refScMarkData = rViewData.GetMarkData(); + // MT IA2: Not used + // int nSelCount = refScMarkData.GetSelectCount(); + bool bIsMark =refScMarkData.IsMarked(); + bool bIsMultMark = refScMarkData.IsMultiMarked(); + bool bNewMarked = refScMarkData.GetTableSelect(aNewCell.Tab()) && ( bIsMark || bIsMultMark ); +// sal_Bool bNewCellSelected = isAccessibleSelected(aNewCell.Row(), aNewCell.Col()); + sal_uInt16 nTab = rViewData.GetTabNo(); + const ScRange& aMarkRange = refScMarkData.GetMarkArea(); + aEvent.OldValue.clear(); + ScDocument* pDoc= GetDocument(mpViewShell); + //Mark All + if ( !bNewPosCellFocus && + (bNewMarked || bIsMark || bIsMultMark ) && + aMarkRange == ScRange( 0,0,nTab, pDoc->MaxCol(),pDoc->MaxRow(),nTab ) ) + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN; + aEvent.NewValue.clear(); + CommitChange(aEvent); + return ; + } + if (!mpMarkedRanges) + { + mpMarkedRanges.reset(new ScRangeList()); + } + refScMarkData.FillRangeListWithMarks(mpMarkedRanges.get(), true); + + //For Whole Col Row + bool bWholeRow = std::abs(aMarkRange.aStart.Row() - aMarkRange.aEnd.Row()) == pDoc->MaxRow() ; + bool bWholeCol = ::abs(aMarkRange.aStart.Col() - aMarkRange.aEnd.Col()) == pDoc->MaxCol() ; + if ((bNewMarked || bIsMark || bIsMultMark ) && (bWholeCol || bWholeRow)) + { + if ( aMarkRange != m_aLastWithInMarkRange ) + { + RemoveSelection(refScMarkData); + if(bNewPosCell) + { + CommitFocusCell(aNewCell); + } + bool bLastIsWholeColRow = + (std::abs(m_aLastWithInMarkRange.aStart.Row() - m_aLastWithInMarkRange.aEnd.Row()) == pDoc->MaxRow() && bWholeRow) || + (::abs(m_aLastWithInMarkRange.aStart.Col() - m_aLastWithInMarkRange.aEnd.Col()) == pDoc->MaxCol() && bWholeCol); + bool bSelSmaller= + bLastIsWholeColRow && + !aMarkRange.Contains(m_aLastWithInMarkRange) && + aMarkRange.Intersects(m_aLastWithInMarkRange); + if( !bSelSmaller ) + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN; + aEvent.NewValue.clear(); + CommitChange(aEvent); + } + m_aLastWithInMarkRange = aMarkRange; + } + return ; + } + m_aLastWithInMarkRange = aMarkRange; + int nNewMarkCount = mpMarkedRanges->GetCellCount(); + bool bSendSingle= (0 == nNewMarkCount) && bNewPosCell; + if (bSendSingle) + { + RemoveSelection(refScMarkData); + if(bNewPosCellFocus) + { + CommitFocusCell(aNewCell); + } + uno::Reference< XAccessible > xChild ; + if (bNewPosCellFocus) + { + xChild = mpAccCell.get(); + } + else + { + mpAccCell = GetAccessibleCellAt(aNewCell.Row(),aNewCell.Col()); + xChild = mpAccCell.get(); + + maActiveCell = aNewCell; + aEvent.EventId = AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS; + aEvent.NewValue <<= xChild; + aEvent.OldValue <<= uno::Reference< XAccessible >(); + CommitChange(aEvent); + } + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + aEvent.NewValue <<= xChild; + CommitChange(aEvent); + OSL_ASSERT(m_mapSelectionSend.count(aNewCell) == 0 ); + m_mapSelectionSend.emplace(aNewCell,xChild); + + } + else + { + ScRange aDelRange; + bool bIsDel = rViewData.GetDelMark( aDelRange ); + if ( (!bIsDel || aMarkRange != aDelRange) && + bNewMarked && + nNewMarkCount > 0 && + m_LastMarkedRanges != *mpMarkedRanges ) + { + RemoveSelection(refScMarkData); + if(bNewPosCellFocus) + { + CommitFocusCell(aNewCell); + } + std::vector<ScMyAddress> vecNew; + if(CalcScRangeListDifferenceMax(mpMarkedRanges.get(), &m_LastMarkedRanges,10,vecNew)) + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN; + aEvent.NewValue.clear(); + CommitChange(aEvent); + } + else + { + for(const auto& rAddr : vecNew) + { + uno::Reference< XAccessible > xChild = getAccessibleCellAt(rAddr.Row(),rAddr.Col()); + if (!(bNewPosCellFocus && rAddr == aNewCell) ) + { + aEvent.EventId = AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS; + aEvent.NewValue <<= xChild; + CommitChange(aEvent); + } + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_ADD; + aEvent.NewValue <<= xChild; + CommitChange(aEvent); + m_mapSelectionSend.emplace(rAddr,xChild); + } + } + } + } + if (bNewPosCellFocus && maActiveCell != aNewCell) + { + CommitFocusCell(aNewCell); + } + m_LastMarkedRanges = *mpMarkedRanges; + } + } + else if (rHint.GetId() == SfxHintId::ScDataChanged) + { + if (!mbDelIns) + CommitTableModelChange(maRange.aStart.Row(), maRange.aStart.Col(), maRange.aEnd.Row(), maRange.aEnd.Col(), AccessibleTableModelChangeType::UPDATE); + else + mbDelIns = false; + if (mpViewShell) + { + ScViewData& rViewData = mpViewShell->GetViewData(); + ScAddress aNewCell = rViewData.GetCurPos(); + if( maActiveCell == aNewCell) + { + ScDocument* pScDoc= GetDocument(mpViewShell); + if (pScDoc) + { + OUString valStr(pScDoc->GetString(aNewCell.Col(),aNewCell.Row(),aNewCell.Tab())); + if(m_strCurCellValue != valStr) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::VALUE_CHANGED; + mpAccCell->CommitChange(aEvent); + m_strCurCellValue=valStr; + } + OUString tabName; + pScDoc->GetName( maActiveCell.Tab(), tabName ); + if( m_strOldTabName != tabName ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + OUString sOldName(ScResId(STR_ACC_TABLE_NAME)); + sOldName = sOldName.replaceFirst("%1", m_strOldTabName); + aEvent.OldValue <<= sOldName; + OUString sNewName(ScResId(STR_ACC_TABLE_NAME)); + sNewName = sNewName.replaceFirst("%1", tabName); + aEvent.NewValue <<= sNewName; + CommitChange( aEvent ); + m_strOldTabName = tabName; + } + } + } + } + } + // commented out, because to use a ModelChangeEvent is not the right way + // at the moment there is no way, but the Java/Gnome Api should be extended sometime +/* if (mpViewShell) + { + Rectangle aNewVisCells(GetVisCells(GetVisArea(mpViewShell, meSplitPos))); + + Rectangle aNewPos(aNewVisCells); + + if (aNewVisCells.Overlaps(maVisCells)) + aNewPos.Union(maVisCells); + else + CommitTableModelChange(maVisCells.Top(), maVisCells.Left(), maVisCells.Bottom(), maVisCells.Right(), AccessibleTableModelChangeType::UPDATE); + + maVisCells = aNewVisCells; + + CommitTableModelChange(aNewPos.Top(), aNewPos.Left(), aNewPos.Bottom(), aNewPos.Right(), AccessibleTableModelChangeType::UPDATE); + } + }*/ + } + + ScAccessibleTableBase::Notify(rBC, rHint); +} + +void ScAccessibleSpreadsheet::RemoveSelection(const ScMarkData &refScMarkData) +{ + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference< XAccessible >(this); + MAP_ADDR_XACC::iterator miRemove = m_mapSelectionSend.begin(); + while (miRemove != m_mapSelectionSend.end()) + { + if (refScMarkData.IsCellMarked(miRemove->first.Col(),miRemove->first.Row(),true) || + refScMarkData.IsCellMarked(miRemove->first.Col(),miRemove->first.Row()) ) + { + ++miRemove; + continue; + } + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE; + aEvent.NewValue <<= miRemove->second; + CommitChange(aEvent); + miRemove = m_mapSelectionSend.erase(miRemove); + } +} +void ScAccessibleSpreadsheet::CommitFocusCell(const ScAddress &aNewCell) +{ + OSL_ASSERT(!IsFormulaMode()); + if(IsFormulaMode()) + { + return ; + } + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::ACTIVE_DESCENDANT_CHANGED; + aEvent.Source = uno::Reference< XAccessible >(this); + aEvent.OldValue <<= uno::Reference<XAccessible>(mpAccCell); + mpAccCell.clear(); + mpAccCell = GetAccessibleCellAt(aNewCell.Row(), aNewCell.Col()); + aEvent.NewValue <<= uno::Reference<XAccessible>(mpAccCell); + maActiveCell = aNewCell; + ScDocument* pScDoc= GetDocument(mpViewShell); + if (pScDoc) + { + m_strCurCellValue = pScDoc->GetString(maActiveCell.Col(),maActiveCell.Row(),maActiveCell.Tab()); + } + CommitChange(aEvent); +} + +//===== XAccessibleTable ================================================ + +uno::Reference< XAccessibleTable > SAL_CALL ScAccessibleSpreadsheet::getAccessibleRowHeaders( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + uno::Reference< XAccessibleTable > xAccessibleTable; + if( mpDoc && mbIsSpreadsheet ) + { + if( std::optional<ScRange> oRowRange = mpDoc->GetRepeatRowRange( mnTab ) ) + { + SCROW nStart = oRowRange->aStart.Row(); + SCROW nEnd = oRowRange->aEnd.Row(); + ScDocument* pDoc = GetDocument(mpViewShell); + if( (0 <= nStart) && (nStart <= nEnd) && (nEnd <= pDoc->MaxRow()) ) + xAccessibleTable.set( new ScAccessibleSpreadsheet( *this, ScRange( 0, nStart, mnTab, pDoc->MaxCol(), nEnd, mnTab ) ) ); + } + } + return xAccessibleTable; +} + +uno::Reference< XAccessibleTable > SAL_CALL ScAccessibleSpreadsheet::getAccessibleColumnHeaders( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + uno::Reference< XAccessibleTable > xAccessibleTable; + if( mpDoc && mbIsSpreadsheet ) + { + if( std::optional<ScRange> oColRange = mpDoc->GetRepeatColRange( mnTab ) ) + { + SCCOL nStart = oColRange->aStart.Col(); + SCCOL nEnd = oColRange->aEnd.Col(); + ScDocument* pDoc = GetDocument(mpViewShell); + if( (0 <= nStart) && (nStart <= nEnd) && (nEnd <= pDoc->MaxCol()) ) + xAccessibleTable.set( new ScAccessibleSpreadsheet( *this, ScRange( nStart, 0, mnTab, nEnd, pDoc->MaxRow(), mnTab ) ) ); + } + } + return xAccessibleTable; +} + +uno::Sequence< sal_Int32 > SAL_CALL ScAccessibleSpreadsheet::getSelectedAccessibleRows( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + uno::Sequence<sal_Int32> aSequence; + if (IsFormulaMode()) + { + return aSequence; + } + if (mpViewShell) + { + aSequence.realloc(maRange.aEnd.Row() - maRange.aStart.Row() + 1); + const ScMarkData& rMarkdata = mpViewShell->GetViewData().GetMarkData(); + sal_Int32* pSequence = aSequence.getArray(); + sal_Int32 nCount(0); + for (SCROW i = maRange.aStart.Row(); i <= maRange.aEnd.Row(); ++i) + { + if (rMarkdata.IsRowMarked(i)) + { + pSequence[nCount] = i; + ++nCount; + } + } + aSequence.realloc(nCount); + } + else + aSequence.realloc(0); + return aSequence; +} + +uno::Sequence< sal_Int32 > SAL_CALL ScAccessibleSpreadsheet::getSelectedAccessibleColumns( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + uno::Sequence<sal_Int32> aSequence; + if (IsFormulaMode() || !mpViewShell) + return aSequence; + + aSequence.realloc(maRange.aEnd.Col() - maRange.aStart.Col() + 1); + sal_Int32* pSequence = aSequence.getArray(); + sal_Int32 nCount(0); + const ScMarkData& rMarkdata = mpViewShell->GetViewData().GetMarkData(); + for (SCCOL i = maRange.aStart.Col(); i <= maRange.aEnd.Col(); ++i) + { + if (rMarkdata.IsColumnMarked(i)) + { + pSequence[nCount] = i; + ++nCount; + } + } + aSequence.realloc(nCount); + return aSequence; +} + +sal_Bool SAL_CALL ScAccessibleSpreadsheet::isAccessibleRowSelected( sal_Int32 nRow ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (IsFormulaMode()) + { + return false; + } + + if ((nRow > (maRange.aEnd.Row() - maRange.aStart.Row())) || (nRow < 0)) + throw lang::IndexOutOfBoundsException(); + + bool bResult(false); + if (mpViewShell) + { + const ScMarkData& rMarkdata = mpViewShell->GetViewData().GetMarkData(); + bResult = rMarkdata.IsRowMarked(static_cast<SCROW>(nRow)); + } + return bResult; +} + +sal_Bool SAL_CALL ScAccessibleSpreadsheet::isAccessibleColumnSelected( sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (IsFormulaMode()) + { + return false; + } + if ((nColumn > (maRange.aEnd.Col() - maRange.aStart.Col())) || (nColumn < 0)) + throw lang::IndexOutOfBoundsException(); + + bool bResult(false); + if (mpViewShell) + { + const ScMarkData& rMarkdata = mpViewShell->GetViewData().GetMarkData(); + bResult = rMarkdata.IsColumnMarked(static_cast<SCCOL>(nColumn)); + } + return bResult; +} + +rtl::Reference<ScAccessibleCell> ScAccessibleSpreadsheet::GetAccessibleCellAt(sal_Int32 nRow, sal_Int32 nColumn) +{ + if (IsFormulaMode()) + { + ScAddress aCellAddress(static_cast<SCCOL>(nColumn), nRow, mpViewShell->GetViewData().GetTabNo()); + if ((aCellAddress == m_aFormulaActiveCell) && m_pAccFormulaCell.is()) + { + return m_pAccFormulaCell; + } + else + { + return ScAccessibleCell::create(this, mpViewShell, aCellAddress, GetAccessibleIndexFormula(nRow, nColumn), meSplitPos, mpAccDoc); + } + } + else + { + ScAddress aCellAddress(static_cast<SCCOL>(maRange.aStart.Col() + nColumn), + static_cast<SCROW>(maRange.aStart.Row() + nRow), maRange.aStart.Tab()); + if ((aCellAddress == maActiveCell) && mpAccCell.is()) + { + return mpAccCell; + } + else + { + return ScAccessibleCell::create(this, mpViewShell, aCellAddress, getAccessibleIndex(nRow, nColumn), meSplitPos, mpAccDoc); + } + } +} + +uno::Reference< XAccessible > SAL_CALL ScAccessibleSpreadsheet::getAccessibleCellAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (!IsFormulaMode()) + { + if (nRow > (maRange.aEnd.Row() - maRange.aStart.Row()) || + nRow < 0 || + nColumn > (maRange.aEnd.Col() - maRange.aStart.Col()) || + nColumn < 0) + throw lang::IndexOutOfBoundsException(); + } + rtl::Reference<ScAccessibleCell> pAccessibleCell = GetAccessibleCellAt(nRow, nColumn); + return pAccessibleCell; +} + +sal_Bool SAL_CALL ScAccessibleSpreadsheet::isAccessibleSelected( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (IsFormulaMode()) + { + ScAddress addr(static_cast<SCCOL>(nColumn), nRow, 0); + return IsScAddrFormulaSel(addr); + } + if ((nColumn > (maRange.aEnd.Col() - maRange.aStart.Col())) || (nColumn < 0) || + (nRow > (maRange.aEnd.Row() - maRange.aStart.Row())) || (nRow < 0)) + throw lang::IndexOutOfBoundsException(); + + bool bResult(false); + if (mpViewShell) + { + const ScMarkData& rMarkdata = mpViewShell->GetViewData().GetMarkData(); + bResult = rMarkdata.IsCellMarked(static_cast<SCCOL>(nColumn), static_cast<SCROW>(nRow)); + } + return bResult; +} + + //===== XAccessibleComponent ============================================ + +uno::Reference< XAccessible > SAL_CALL ScAccessibleSpreadsheet::getAccessibleAtPoint(const awt::Point& rPoint) +{ + uno::Reference< XAccessible > xAccessible; + if (containsPoint(rPoint)) + { + SolarMutexGuard aGuard; + IsObjectValid(); + if (mpViewShell) + { + SCCOL nX; + SCROW nY; + mpViewShell->GetViewData().GetPosFromPixel( rPoint.X, rPoint.Y, meSplitPos, nX, nY); + try { + xAccessible = getAccessibleCellAt(nY, nX); + } + catch(const css::lang::IndexOutOfBoundsException &) + { + return nullptr; + } + } + } + return xAccessible; +} + +void SAL_CALL ScAccessibleSpreadsheet::grabFocus( ) +{ + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleComponent> xAccessibleComponent(getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY); + if (xAccessibleComponent.is()) + xAccessibleComponent->grabFocus(); + } +} + +sal_Int32 SAL_CALL ScAccessibleSpreadsheet::getForeground( ) +{ + return sal_Int32(COL_BLACK); +} + +sal_Int32 SAL_CALL ScAccessibleSpreadsheet::getBackground( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return sal_Int32(SC_MOD()->GetColorConfig().GetColorValue( ::svtools::DOCCOLOR ).nColor); +} + + //===== XAccessibleContext ============================================== + +uno::Reference<XAccessibleRelationSet> SAL_CALL ScAccessibleSpreadsheet::getAccessibleRelationSet() +{ + rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSet; + if(mpAccDoc) + pRelationSet = mpAccDoc->GetRelationSet(nullptr); + if (pRelationSet) + return pRelationSet; + return new utl::AccessibleRelationSetHelper(); +} + +sal_Int64 SAL_CALL ScAccessibleSpreadsheet::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + sal_Int64 nStateSet = 0; + if (IsDefunc(nParentStates)) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + nStateSet |= AccessibleStateType::MANAGES_DESCENDANTS; + if (IsEditable()) + nStateSet |= AccessibleStateType::EDITABLE; + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::FOCUSABLE; + if (IsFocused()) + nStateSet |= AccessibleStateType::FOCUSED; + nStateSet |= AccessibleStateType::MULTI_SELECTABLE; + nStateSet |= AccessibleStateType::OPAQUE; + nStateSet |= AccessibleStateType::SELECTABLE; + if (IsCompleteSheetSelected()) + nStateSet |= AccessibleStateType::SELECTED; + if (isShowing()) + nStateSet |= AccessibleStateType::SHOWING; + if (isVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + } + return nStateSet; +} + + ///===== XAccessibleSelection =========================================== + +void SAL_CALL ScAccessibleSpreadsheet::selectAccessibleChild( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (nChildIndex < 0 || nChildIndex >= getAccessibleChildCount()) + throw lang::IndexOutOfBoundsException(); + + if (mpViewShell) + { + sal_Int32 nCol(getAccessibleColumn(nChildIndex)); + sal_Int32 nRow(getAccessibleRow(nChildIndex)); + + SelectCell(nRow, nCol, false); + } +} + +void SAL_CALL + ScAccessibleSpreadsheet::clearAccessibleSelection( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (mpViewShell && !IsFormulaMode()) + mpViewShell->Unmark(); +} + +void SAL_CALL ScAccessibleSpreadsheet::selectAllAccessibleChildren( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + if (!mpViewShell) + return; + + if (IsFormulaMode()) + { + ScDocument* pDoc = GetDocument(mpViewShell); + ScViewData& rViewData = mpViewShell->GetViewData(); + mpViewShell->InitRefMode( 0, 0, rViewData.GetTabNo(), SC_REFTYPE_REF ); + rViewData.SetRefStart(0, 0, rViewData.GetTabNo()); + rViewData.SetRefEnd(pDoc->MaxCol(), pDoc->MaxRow(), rViewData.GetTabNo()); + mpViewShell->UpdateRef(pDoc->MaxCol(), pDoc->MaxRow(), rViewData.GetTabNo()); + } + else + mpViewShell->SelectAll(); +} + +sal_Int64 SAL_CALL + ScAccessibleSpreadsheet::getSelectedAccessibleChildCount( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + sal_Int64 nResult(0); + if (mpViewShell) + { + if (IsFormulaMode()) + { + nResult = static_cast<sal_Int64>(GetRowAll()) * static_cast<sal_Int64>(GetColAll()); + } + else + { + if (!mpMarkedRanges) + { + mpMarkedRanges.reset(new ScRangeList()); + ScMarkData aMarkData(mpViewShell->GetViewData().GetMarkData()); + aMarkData.FillRangeListWithMarks(mpMarkedRanges.get(), false); + } + // is possible, because there shouldn't be overlapped ranges in it + if (mpMarkedRanges) + nResult = mpMarkedRanges->GetCellCount(); + } + } + return nResult; +} + +uno::Reference<XAccessible > SAL_CALL + ScAccessibleSpreadsheet::getSelectedAccessibleChild( sal_Int64 nSelectedChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + uno::Reference < XAccessible > xAccessible; + if (IsFormulaMode()) + { + if(CheckChildIndex(nSelectedChildIndex)) + { + ScAddress addr = GetChildIndexAddress(nSelectedChildIndex); + xAccessible = getAccessibleCellAt(addr.Row(), addr.Col()); + } + return xAccessible; + } + if (mpViewShell) + { + if (!mpMarkedRanges) + { + mpMarkedRanges.reset(new ScRangeList()); + mpViewShell->GetViewData().GetMarkData().FillRangeListWithMarks(mpMarkedRanges.get(), false); + } + if (mpMarkedRanges) + { + if ((nSelectedChildIndex < 0) || + (mpMarkedRanges->GetCellCount() <= o3tl::make_unsigned(nSelectedChildIndex))) + { + throw lang::IndexOutOfBoundsException(); + } + ScMyAddress addr = CalcScAddressFromRangeList(mpMarkedRanges.get(),nSelectedChildIndex); + auto it = m_mapSelectionSend.find(addr); + if( it != m_mapSelectionSend.end() ) + xAccessible = it->second; + else + xAccessible = getAccessibleCellAt(addr.Row(), addr.Col()); + } + } + return xAccessible; +} + +void SAL_CALL ScAccessibleSpreadsheet::deselectAccessibleChild( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (nChildIndex < 0 || nChildIndex >= getAccessibleChildCount()) + throw lang::IndexOutOfBoundsException(); + + if (!mpViewShell) + return; + + sal_Int32 nCol(getAccessibleColumn(nChildIndex)); + sal_Int32 nRow(getAccessibleRow(nChildIndex)); + + if (IsFormulaMode()) + { + if(IsScAddrFormulaSel( + ScAddress(static_cast<SCCOL>(nCol), nRow,mpViewShell->GetViewData().GetTabNo())) + ) + { + SelectCell(nRow, nCol, true); + } + return ; + } + if (mpViewShell->GetViewData().GetMarkData().IsCellMarked(static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow))) + SelectCell(nRow, nCol, true); +} + +void ScAccessibleSpreadsheet::SelectCell(sal_Int32 nRow, sal_Int32 nCol, bool bDeselect) +{ + if (IsFormulaMode()) + { + if (bDeselect) + {//?? + return; + } + else + { + ScViewData& rViewData = mpViewShell->GetViewData(); + + mpViewShell->InitRefMode( static_cast<SCCOL>(nCol), nRow, rViewData.GetTabNo(), SC_REFTYPE_REF ); + mpViewShell->UpdateRef(static_cast<SCCOL>(nCol), nRow, rViewData.GetTabNo()); + } + return ; + } + mpViewShell->SetTabNo( maRange.aStart.Tab() ); + + mpViewShell->DoneBlockMode( true ); // continue selecting + mpViewShell->InitBlockMode( static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), maRange.aStart.Tab(), bDeselect ); + + mpViewShell->SelectionChanged(); +} + +/* +void ScAccessibleSpreadsheet::CreateSortedMarkedCells() +{ + mpSortedMarkedCells = new std::vector<ScMyAddress>(); + mpSortedMarkedCells->reserve(mpMarkedRanges->GetCellCount()); + for ( size_t i = 0, ListSize = mpMarkedRanges->size(); i < ListSize; ++i ) + { + ScRange* pRange = (*mpMarkedRanges)[i]; + if (pRange->aStart.Tab() != pRange->aEnd.Tab()) + { + if ((maActiveCell.Tab() >= pRange->aStart.Tab()) || + maActiveCell.Tab() <= pRange->aEnd.Tab()) + { + ScRange aRange(*pRange); + aRange.aStart.SetTab(maActiveCell.Tab()); + aRange.aEnd.SetTab(maActiveCell.Tab()); + AddMarkedRange(aRange); + } + else + { + OSL_FAIL("Range of wrong table"); + } + } + else if(pRange->aStart.Tab() == maActiveCell.Tab()) + AddMarkedRange(*pRange); + else + { + OSL_FAIL("Range of wrong table"); + } + } + std::sort(mpSortedMarkedCells->begin(), mpSortedMarkedCells->end()); +} + +void ScAccessibleSpreadsheet::AddMarkedRange(const ScRange& rRange) +{ + for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow) + { + for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol) + { + ScMyAddress aCell(nCol, nRow, maActiveCell.Tab()); + mpSortedMarkedCells->push_back(aCell); + } + } +}*/ + + //===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessibleSpreadsheet::getImplementationName() +{ + return "ScAccessibleSpreadsheet"; +} + +uno::Sequence< OUString> SAL_CALL + ScAccessibleSpreadsheet::getSupportedServiceNames() +{ + const css::uno::Sequence<OUString> vals { "com.sun.star.AccessibleSpreadsheet" }; + return comphelper::concatSequences(ScAccessibleContextBase::getSupportedServiceNames(), vals); +} + +//===== XTypeProvider ======================================================= + +uno::Sequence<sal_Int8> SAL_CALL + ScAccessibleSpreadsheet::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +///===== XAccessibleEventBroadcaster ===================================== + +void SAL_CALL ScAccessibleSpreadsheet::addAccessibleEventListener(const uno::Reference<XAccessibleEventListener>& xListener) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + ScAccessibleTableBase::addAccessibleEventListener(xListener); + +} + +//==== internal ========================================================= + +AbsoluteScreenPixelRectangle ScAccessibleSpreadsheet::GetBoundingBoxOnScreen() const +{ + AbsoluteScreenPixelRectangle aRect; + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindowByPos(meSplitPos); + if (pWindow) + aRect = pWindow->GetWindowExtentsAbsolute(); + } + return aRect; +} + +tools::Rectangle ScAccessibleSpreadsheet::GetBoundingBox() const +{ + tools::Rectangle aRect; + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindowByPos(meSplitPos); + if (pWindow) + //#101986#; extends to the same window, because the parent is the document and it has the same window + aRect = pWindow->GetWindowExtentsRelative(*pWindow); + } + return aRect; +} + +bool ScAccessibleSpreadsheet::IsDefunc(sal_Int64 nParentStates) +{ + return ScAccessibleContextBase::IsDefunc() || (mpViewShell == nullptr) || !getAccessibleParent().is() || + (nParentStates & AccessibleStateType::DEFUNC); +} + +bool ScAccessibleSpreadsheet::IsEditable() +{ + if (IsFormulaMode()) + { + return false; + } + bool bProtected(false); + if (mpDoc && mpDoc->IsTabProtected(maRange.aStart.Tab())) + bProtected = true; + return !bProtected; +} + +bool ScAccessibleSpreadsheet::IsFocused() +{ + bool bFocused(false); + if (mpViewShell) + { + if (mpViewShell->GetViewData().GetActivePart() == meSplitPos) + bFocused = mpViewShell->GetActiveWin()->HasFocus(); + } + return bFocused; +} + +bool ScAccessibleSpreadsheet::IsCompleteSheetSelected() +{ + if (IsFormulaMode()) + { + return false; + } + + bool bResult(false); + if(mpViewShell) + { + //#103800#; use a copy of MarkData + ScMarkData aMarkData(mpViewShell->GetViewData().GetMarkData()); + if (aMarkData.IsAllMarked(maRange)) + bResult = true; + } + return bResult; +} + +ScDocument* ScAccessibleSpreadsheet::GetDocument(ScTabViewShell* pViewShell) +{ + ScDocument* pDoc = nullptr; + if (pViewShell) + pDoc = &pViewShell->GetViewData().GetDocument(); + return pDoc; +} + +sal_Bool SAL_CALL ScAccessibleSpreadsheet::selectRow( sal_Int32 row ) +{ + SolarMutexGuard g; + + if (IsFormulaMode()) + { + return false; + } + + ScDocument* pDoc = GetDocument(mpViewShell); + mpViewShell->SetTabNo( maRange.aStart.Tab() ); + mpViewShell->DoneBlockMode( true ); // continue selecting + mpViewShell->InitBlockMode( 0, row, maRange.aStart.Tab(), false, false, true ); + mpViewShell->MarkCursor( pDoc->MaxCol(), row, maRange.aStart.Tab(), false, true ); + mpViewShell->SelectionChanged(); + return true; +} + +sal_Bool SAL_CALL ScAccessibleSpreadsheet::selectColumn( sal_Int32 column ) +{ + SolarMutexGuard g; + + if (IsFormulaMode()) + { + return false; + } + + ScDocument* pDoc = GetDocument(mpViewShell); + mpViewShell->SetTabNo( maRange.aStart.Tab() ); + mpViewShell->DoneBlockMode( true ); // continue selecting + mpViewShell->InitBlockMode( static_cast<SCCOL>(column), 0, maRange.aStart.Tab(), false, true ); + mpViewShell->MarkCursor( static_cast<SCCOL>(column), pDoc->MaxRow(), maRange.aStart.Tab(), true ); + mpViewShell->SelectionChanged(); + return true; +} + +sal_Bool SAL_CALL ScAccessibleSpreadsheet::unselectRow( sal_Int32 row ) +{ + SolarMutexGuard g; + + if (IsFormulaMode()) + { + return false; + } + + ScDocument* pDoc = GetDocument(mpViewShell); + mpViewShell->SetTabNo( maRange.aStart.Tab() ); + mpViewShell->DoneBlockMode( true ); // continue selecting + mpViewShell->InitBlockMode( 0, row, maRange.aStart.Tab(), false, false, true, true ); + mpViewShell->MarkCursor( pDoc->MaxCol(), row, maRange.aStart.Tab(), false, true ); + mpViewShell->SelectionChanged(); + mpViewShell->DoneBlockMode( true ); + return true; +} + +sal_Bool SAL_CALL ScAccessibleSpreadsheet::unselectColumn( sal_Int32 column ) +{ + SolarMutexGuard g; + + if (IsFormulaMode()) + { + return false; + } + + ScDocument* pDoc = GetDocument(mpViewShell); + mpViewShell->SetTabNo( maRange.aStart.Tab() ); + mpViewShell->DoneBlockMode( true ); // continue selecting + mpViewShell->InitBlockMode( static_cast<SCCOL>(column), 0, maRange.aStart.Tab(), false, true, false, true ); + mpViewShell->MarkCursor( static_cast<SCCOL>(column), pDoc->MaxRow(), maRange.aStart.Tab(), true ); + mpViewShell->SelectionChanged(); + mpViewShell->DoneBlockMode( true ); + return true; +} + +void ScAccessibleSpreadsheet::FireFirstCellFocus() +{ + if (IsFormulaMode()) + { + return ; + } + if (mbIsFocusSend) + { + return ; + } + mbIsFocusSend = true; + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::ACTIVE_DESCENDANT_CHANGED; + aEvent.Source = uno::Reference< XAccessible >(this); + aEvent.NewValue <<= getAccessibleCellAt(maActiveCell.Row(), maActiveCell.Col()); + CommitChange(aEvent); +} + +void ScAccessibleSpreadsheet::NotifyRefMode() +{ + ScViewData& rViewData = mpViewShell->GetViewData(); + if (!rViewData.IsRefMode()) + // Not in reference mode. Bail out. + return; + + sal_uInt16 nRefStartX = rViewData.GetRefStartX(); + sal_Int32 nRefStartY = rViewData.GetRefStartY(); + sal_uInt16 nRefEndX = rViewData.GetRefEndX(); + sal_Int32 nRefEndY = rViewData.GetRefEndY(); + ScAddress aFormulaAddr; + if(!GetFormulaCurrentFocusCell(aFormulaAddr)) + { + return ; + } + if (m_aFormulaActiveCell != aFormulaAddr) + {//New Focus + m_nMinX =std::min(nRefStartX,nRefEndX); + m_nMaxX =std::max(nRefStartX,nRefEndX); + m_nMinY = std::min(nRefStartY,nRefEndY); + m_nMaxY = std::max(nRefStartY,nRefEndY); + RemoveFormulaSelection(); + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference< XAccessible >(this); + aEvent.EventId = AccessibleEventId::ACTIVE_DESCENDANT_CHANGED; + aEvent.OldValue <<= uno::Reference<XAccessible>(m_pAccFormulaCell); + m_pAccFormulaCell = GetAccessibleCellAt(aFormulaAddr.Row(), aFormulaAddr.Col()); + uno::Reference< XAccessible > xNew = m_pAccFormulaCell; + aEvent.NewValue <<= xNew; + CommitChange(aEvent); + if (nRefStartX == nRefEndX && nRefStartY == nRefEndY) + {//Selection Single + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + aEvent.NewValue <<= xNew; + CommitChange(aEvent); + m_mapFormulaSelectionSend.emplace(aFormulaAddr,xNew); + m_vecFormulaLastMyAddr.clear(); + m_vecFormulaLastMyAddr.emplace_back(aFormulaAddr); + } + else + { + std::vector<ScMyAddress> vecCurSel; + int nCurSize = (m_nMaxX - m_nMinX +1)*(m_nMaxY - m_nMinY +1) ; + vecCurSel.reserve(nCurSize); + for (sal_uInt16 x = m_nMinX ; x <= m_nMaxX ; ++x) + { + for (sal_Int32 y = m_nMinY ; y <= m_nMaxY ; ++y) + { + ScMyAddress aAddr(x,y,0); + vecCurSel.push_back(aAddr); + } + } + std::sort(vecCurSel.begin(), vecCurSel.end()); + std::vector<ScMyAddress> vecNew; + std::set_difference(vecCurSel.begin(),vecCurSel.end(), + m_vecFormulaLastMyAddr.begin(),m_vecFormulaLastMyAddr.end(), + std::back_insert_iterator(vecNew)); + int nNewSize = vecNew.size(); + if ( nNewSize > 10 ) + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN; + aEvent.NewValue.clear(); + CommitChange(aEvent); + } + else + { + for(const auto& rAddr : vecNew) + { + uno::Reference< XAccessible > xChild; + if (rAddr == aFormulaAddr) + { + xChild = m_pAccFormulaCell.get(); + } + else + { + xChild = getAccessibleCellAt(rAddr.Row(),rAddr.Col()); + aEvent.EventId = AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS; + aEvent.NewValue <<= xChild; + CommitChange(aEvent); + } + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_ADD; + aEvent.NewValue <<= xChild; + CommitChange(aEvent); + m_mapFormulaSelectionSend.emplace(rAddr,xChild); + } + } + m_vecFormulaLastMyAddr.swap(vecCurSel); + } + } + m_aFormulaActiveCell = aFormulaAddr; +} + +void ScAccessibleSpreadsheet::RemoveFormulaSelection(bool bRemoveAll ) +{ + AccessibleEventObject aEvent; + aEvent.Source = uno::Reference< XAccessible >(this); + MAP_ADDR_XACC::iterator miRemove = m_mapFormulaSelectionSend.begin(); + while (miRemove != m_mapFormulaSelectionSend.end()) + { + if( !bRemoveAll && IsScAddrFormulaSel(miRemove->first) ) + { + ++miRemove; + continue; + } + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE; + aEvent.NewValue <<= miRemove->second; + CommitChange(aEvent); + miRemove = m_mapFormulaSelectionSend.erase(miRemove); + } +} + +bool ScAccessibleSpreadsheet::IsScAddrFormulaSel(const ScAddress &addr) const +{ + return addr.Col() >= m_nMinX && addr.Col() <= m_nMaxX && + addr.Row() >= m_nMinY && addr.Row() <= m_nMaxY && + addr.Tab() == mpViewShell->GetViewData().GetTabNo(); +} + +bool ScAccessibleSpreadsheet::CheckChildIndex(sal_Int64 nIndex) const +{ + sal_Int64 nMaxIndex = static_cast<sal_Int64>(m_nMaxX - m_nMinX +1) * static_cast<sal_Int64>(m_nMaxY - m_nMinY +1) -1 ; + return nIndex <= nMaxIndex && nIndex >= 0 ; +} + +ScAddress ScAccessibleSpreadsheet::GetChildIndexAddress(sal_Int64 nIndex) const +{ + sal_Int64 nRowAll = GetRowAll(); + sal_Int64 nColAll = GetColAll(); + if (nIndex < 0 || nIndex >= nRowAll * nColAll) + { + return ScAddress(); + } + return ScAddress( + static_cast<SCCOL>((nIndex - nIndex % nRowAll) / nRowAll + + m_nMinX), + nIndex % nRowAll + m_nMinY, + mpViewShell->GetViewData().GetTabNo() + ); +} + +sal_Int64 ScAccessibleSpreadsheet::GetAccessibleIndexFormula( sal_Int32 nRow, sal_Int32 nColumn ) +{ + sal_uInt16 nColRelative = sal_uInt16(nColumn) - GetColAll(); + sal_Int32 nRowRelative = nRow - GetRowAll(); + if (nRow < 0 || nColumn < 0 || nRowRelative >= GetRowAll() || nColRelative >= GetColAll() ) + { + return -1; + } + return static_cast<sal_Int64>(GetRowAll()) * static_cast<sal_Int64>(nRowRelative) + nColRelative; +} + +bool ScAccessibleSpreadsheet::IsFormulaMode() +{ + ScViewData& rViewData = mpViewShell->GetViewData(); + m_bFormulaMode = rViewData.IsRefMode() || SC_MOD()->IsFormulaMode(); + return m_bFormulaMode ; +} + +bool ScAccessibleSpreadsheet::GetFormulaCurrentFocusCell(ScAddress &addr) +{ + ScViewData& rViewData = mpViewShell->GetViewData(); + sal_uInt16 nRefX=0; + sal_Int32 nRefY=0; + if(m_bFormulaLastMode) + { + nRefX=rViewData.GetRefEndX(); + nRefY=rViewData.GetRefEndY(); + } + else + { + nRefX=rViewData.GetRefStartX(); + nRefY=rViewData.GetRefStartY(); + } + ScDocument* pDoc = GetDocument(mpViewShell); + if( /* Always true: nRefX >= 0 && */ nRefX <= pDoc->MaxCol() && nRefY >= 0 && nRefY <= pDoc->MaxRow()) + { + addr = ScAddress(nRefX,nRefY,rViewData.GetTabNo()); + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleTableBase.cxx b/sc/source/ui/Accessibility/AccessibleTableBase.cxx new file mode 100644 index 0000000000..5ce54fad56 --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleTableBase.cxx @@ -0,0 +1,466 @@ +/* -*- 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 <AccessibleTableBase.hxx> +#include <document.hxx> +#include <scresid.hxx> +#include <strings.hrc> +#include <strings.hxx> +#include <table.hxx> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <comphelper/sequence.hxx> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +//===== internal ============================================================ + +ScAccessibleTableBase::ScAccessibleTableBase( + const uno::Reference<XAccessible>& rxParent, + ScDocument* pDoc, + const ScRange& rRange) + : + ScAccessibleContextBase (rxParent, AccessibleRole::TABLE), + maRange(rRange), + mpDoc(pDoc) +{ +} + +ScAccessibleTableBase::~ScAccessibleTableBase() +{ +} + +void SAL_CALL ScAccessibleTableBase::disposing() +{ + SolarMutexGuard aGuard; + mpDoc = nullptr; + + ScAccessibleContextBase::disposing(); +} + + //===== XInterface ===================================================== + +uno::Any SAL_CALL ScAccessibleTableBase::queryInterface( uno::Type const & rType ) +{ + if ( rType == cppu::UnoType<XAccessibleTableSelection>::get()) + { + return uno::Any(uno::Reference<XAccessibleTableSelection>(this)); + } + else + { + uno::Any aAny (ScAccessibleTableBaseImpl::queryInterface(rType)); + return aAny.hasValue() ? aAny : ScAccessibleContextBase::queryInterface(rType); + } +} + +void SAL_CALL ScAccessibleTableBase::acquire() + noexcept +{ + ScAccessibleContextBase::acquire(); +} + +void SAL_CALL ScAccessibleTableBase::release() + noexcept +{ + ScAccessibleContextBase::release(); +} + + //===== XAccessibleTable ================================================ + +sal_Int32 SAL_CALL ScAccessibleTableBase::getAccessibleRowCount( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return maRange.aEnd.Row() - maRange.aStart.Row() + 1; +} + +sal_Int32 SAL_CALL ScAccessibleTableBase::getAccessibleColumnCount( ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + return maRange.aEnd.Col() - maRange.aStart.Col() + 1; +} + +OUString SAL_CALL ScAccessibleTableBase::getAccessibleRowDescription( sal_Int32 nRow ) +{ + OSL_FAIL("Here should be an implementation to fill the description"); + + if ((nRow > (maRange.aEnd.Row() - maRange.aStart.Row())) || (nRow < 0)) + throw lang::IndexOutOfBoundsException(); + + //setAccessibleRowDescription(nRow, xAccessible); // to remember the created Description + return OUString(); +} + +OUString SAL_CALL ScAccessibleTableBase::getAccessibleColumnDescription( sal_Int32 nColumn ) +{ + OSL_FAIL("Here should be an implementation to fill the description"); + + if ((nColumn > (maRange.aEnd.Col() - maRange.aStart.Col())) || (nColumn < 0)) + throw lang::IndexOutOfBoundsException(); + + //setAccessibleColumnDescription(nColumn, xAccessible); // to remember the created Description + return OUString(); +} + +sal_Int32 SAL_CALL ScAccessibleTableBase::getAccessibleRowExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if ((nColumn > (maRange.aEnd.Col() - maRange.aStart.Col())) || (nColumn < 0) || + (nRow > (maRange.aEnd.Row() - maRange.aStart.Row())) || (nRow < 0)) + throw lang::IndexOutOfBoundsException(); + + sal_Int32 nCount(1); // the same cell + nRow += maRange.aStart.Row(); + nColumn += maRange.aStart.Col(); + + if (mpDoc) + { + ScTable* pTab = mpDoc->FetchTable(maRange.aStart.Tab()); + if (pTab) + { + SCROW nStartRow = static_cast<SCROW>(nRow); + SCROW nEndRow = nStartRow; + SCCOL nStartCol = static_cast<SCCOL>(nColumn); + SCCOL nEndCol = nStartCol; + if (pTab->ExtendMerge( nStartCol, nStartRow, nEndCol, nEndRow, false)) + { + if (nEndRow > nStartRow) + nCount = nEndRow - nStartRow + 1; + } + } + } + + return nCount; +} + +sal_Int32 SAL_CALL ScAccessibleTableBase::getAccessibleColumnExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if ((nColumn > (maRange.aEnd.Col() - maRange.aStart.Col())) || (nColumn < 0) || + (nRow > (maRange.aEnd.Row() - maRange.aStart.Row())) || (nRow < 0)) + throw lang::IndexOutOfBoundsException(); + + sal_Int32 nCount(1); // the same cell + nRow += maRange.aStart.Row(); + nColumn += maRange.aStart.Col(); + + if (mpDoc) + { + ScTable* pTab = mpDoc->FetchTable(maRange.aStart.Tab()); + if (pTab) + { + SCROW nStartRow = static_cast<SCROW>(nRow); + SCROW nEndRow = nStartRow; + SCCOL nStartCol = static_cast<SCCOL>(nColumn); + SCCOL nEndCol = nStartCol; + if (pTab->ExtendMerge( nStartCol, nStartRow, nEndCol, nEndRow, false)) + { + if (nEndCol > nStartCol) + nCount = nEndCol - nStartCol + 1; + } + } + } + + return nCount; +} + +uno::Reference< XAccessibleTable > SAL_CALL ScAccessibleTableBase::getAccessibleRowHeaders( ) +{ + uno::Reference< XAccessibleTable > xAccessibleTable; + OSL_FAIL("Here should be an implementation to fill the row headers"); + + //CommitChange + return xAccessibleTable; +} + +uno::Reference< XAccessibleTable > SAL_CALL ScAccessibleTableBase::getAccessibleColumnHeaders( ) +{ + uno::Reference< XAccessibleTable > xAccessibleTable; + OSL_FAIL("Here should be an implementation to fill the column headers"); + + //CommitChange + return xAccessibleTable; +} + +uno::Sequence< sal_Int32 > SAL_CALL ScAccessibleTableBase::getSelectedAccessibleRows( ) +{ + OSL_FAIL("not implemented yet"); + uno::Sequence< sal_Int32 > aSequence; + return aSequence; +} + +uno::Sequence< sal_Int32 > SAL_CALL ScAccessibleTableBase::getSelectedAccessibleColumns( ) +{ + OSL_FAIL("not implemented yet"); + uno::Sequence< sal_Int32 > aSequence; + return aSequence; +} + +sal_Bool SAL_CALL ScAccessibleTableBase::isAccessibleRowSelected( sal_Int32 /* nRow */ ) +{ + OSL_FAIL("not implemented yet"); + return false; +} + +sal_Bool SAL_CALL ScAccessibleTableBase::isAccessibleColumnSelected( sal_Int32 /* nColumn */ ) +{ + OSL_FAIL("not implemented yet"); + return false; +} + +uno::Reference< XAccessible > SAL_CALL ScAccessibleTableBase::getAccessibleCellAt( sal_Int32 /* nRow */, sal_Int32 /* nColumn */ ) +{ + OSL_FAIL("not implemented yet"); + uno::Reference< XAccessible > xAccessible; + return xAccessible; +} + +uno::Reference< XAccessible > SAL_CALL ScAccessibleTableBase::getAccessibleCaption( ) +{ + OSL_FAIL("not implemented yet"); + uno::Reference< XAccessible > xAccessible; + return xAccessible; +} + +uno::Reference< XAccessible > SAL_CALL ScAccessibleTableBase::getAccessibleSummary( ) +{ + OSL_FAIL("not implemented yet"); + uno::Reference< XAccessible > xAccessible; + return xAccessible; +} + +sal_Bool SAL_CALL ScAccessibleTableBase::isAccessibleSelected( sal_Int32 /* nRow */, sal_Int32 /* nColumn */ ) +{ + OSL_FAIL("not implemented yet"); + return false; +} + +// ===== XAccessibleExtendedTable ======================================== + +sal_Int64 SAL_CALL ScAccessibleTableBase::getAccessibleIndex( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (nRow > (maRange.aEnd.Row() - maRange.aStart.Row()) || + nRow < 0 || + nColumn > (maRange.aEnd.Col() - maRange.aStart.Col()) || + nColumn < 0) + throw lang::IndexOutOfBoundsException(); + + nRow -= maRange.aStart.Row(); + nColumn -= maRange.aStart.Col(); + return (static_cast<sal_Int64>(nRow) * static_cast<sal_Int64>(maRange.aEnd.Col() + 1)) + nColumn; +} + +sal_Int32 SAL_CALL ScAccessibleTableBase::getAccessibleRow( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (nChildIndex >= getAccessibleChildCount() || nChildIndex < 0) + throw lang::IndexOutOfBoundsException(); + + return nChildIndex / (maRange.aEnd.Col() - maRange.aStart.Col() + 1); +} + +sal_Int32 SAL_CALL ScAccessibleTableBase::getAccessibleColumn( sal_Int64 nChildIndex ) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (nChildIndex >= getAccessibleChildCount() || nChildIndex < 0) + throw lang::IndexOutOfBoundsException(); + + return nChildIndex % static_cast<sal_Int32>(maRange.aEnd.Col() - maRange.aStart.Col() + 1); +} + +// ===== XAccessibleContext ============================================== + +sal_Int64 SAL_CALL ScAccessibleTableBase::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + // FIXME: representing rows & columns this way is a plain and simple madness. + // this needs a radical re-think. + sal_Int64 nMax = static_cast<sal_Int64>(maRange.aEnd.Row() - maRange.aStart.Row() + 1) * + static_cast<sal_Int64>(maRange.aEnd.Col() - maRange.aStart.Col() + 1); + if (nMax < 0) + return 0; + return nMax; +} + +uno::Reference< XAccessible > SAL_CALL + ScAccessibleTableBase::getAccessibleChild(sal_Int64 nIndex) +{ + SolarMutexGuard aGuard; + IsObjectValid(); + + if (nIndex >= getAccessibleChildCount() || nIndex < 0) + throw lang::IndexOutOfBoundsException(); + + // FIXME: representing rows & columns this way is a plain and simple madness. + // this needs a radical re-think. + + sal_Int32 nRow(0); + sal_Int32 nColumn(0); + sal_Int32 nTemp(maRange.aEnd.Col() - maRange.aStart.Col() + 1); + nRow = nIndex / nTemp; + nColumn = nIndex % nTemp; + return getAccessibleCellAt(nRow, nColumn); +} + +OUString + ScAccessibleTableBase::createAccessibleDescription() +{ + return STR_ACC_TABLE_DESCR; +} + +OUString ScAccessibleTableBase::createAccessibleName() +{ + OUString sName(ScResId(STR_ACC_TABLE_NAME)); + OUString sCoreName; + if (mpDoc && mpDoc->GetName( maRange.aStart.Tab(), sCoreName )) + sName = sName.replaceFirst("%1", sCoreName); + return sName; +} + +uno::Reference<XAccessibleRelationSet> SAL_CALL + ScAccessibleTableBase::getAccessibleRelationSet() +{ + OSL_FAIL("should be implemented in the abrevated class"); + return uno::Reference<XAccessibleRelationSet>(); +} + +sal_Int64 SAL_CALL ScAccessibleTableBase::getAccessibleStateSet() +{ + OSL_FAIL("should be implemented in the abrevated class"); + return 0; +} + + ///===== XAccessibleSelection =========================================== + +void SAL_CALL ScAccessibleTableBase::selectAccessibleChild( sal_Int64 /* nChildIndex */ ) +{ +} + +sal_Bool SAL_CALL + ScAccessibleTableBase::isAccessibleChildSelected( sal_Int64 nChildIndex ) +{ + // I don't need to guard, because the called functions have a guard + if (nChildIndex < 0 || nChildIndex >= getAccessibleChildCount()) + throw lang::IndexOutOfBoundsException(); + return isAccessibleSelected(getAccessibleRow(nChildIndex), getAccessibleColumn(nChildIndex)); +} + +void SAL_CALL + ScAccessibleTableBase::clearAccessibleSelection( ) +{ +} + +void SAL_CALL ScAccessibleTableBase::selectAllAccessibleChildren() +{ +} + +sal_Int64 SAL_CALL + ScAccessibleTableBase::getSelectedAccessibleChildCount( ) +{ + return 0; +} + +uno::Reference<XAccessible > SAL_CALL + ScAccessibleTableBase::getSelectedAccessibleChild( sal_Int64 /* nSelectedChildIndex */ ) +{ + uno::Reference < XAccessible > xAccessible; + return xAccessible; +} + +void SAL_CALL ScAccessibleTableBase::deselectAccessibleChild( sal_Int64 /* nSelectedChildIndex */ ) +{ +} + + //===== XServiceInfo ==================================================== + +OUString SAL_CALL ScAccessibleTableBase::getImplementationName() +{ + return "ScAccessibleTableBase"; +} + + //===== XTypeProvider =================================================== + +uno::Sequence< uno::Type > SAL_CALL ScAccessibleTableBase::getTypes() +{ + return comphelper::concatSequences(ScAccessibleTableBaseImpl::getTypes(), ScAccessibleContextBase::getTypes()); +} + +uno::Sequence<sal_Int8> SAL_CALL + ScAccessibleTableBase::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +void ScAccessibleTableBase::CommitTableModelChange(sal_Int32 nStartRow, sal_Int32 nStartCol, sal_Int32 nEndRow, sal_Int32 nEndCol, sal_uInt16 nId) +{ + AccessibleTableModelChange aModelChange; + aModelChange.FirstRow = nStartRow; + aModelChange.FirstColumn = nStartCol; + aModelChange.LastRow = nEndRow; + aModelChange.LastColumn = nEndCol; + aModelChange.Type = nId; + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TABLE_MODEL_CHANGED; + aEvent.Source = uno::Reference< XAccessibleContext >(this); + aEvent.NewValue <<= aModelChange; + + CommitChange(aEvent); +} + +sal_Bool SAL_CALL ScAccessibleTableBase::selectRow( sal_Int32 ) +{ + return true; +} + +sal_Bool SAL_CALL ScAccessibleTableBase::selectColumn( sal_Int32 ) +{ + return true; +} + +sal_Bool SAL_CALL ScAccessibleTableBase::unselectRow( sal_Int32 ) +{ + return true; +} + +sal_Bool SAL_CALL ScAccessibleTableBase::unselectColumn( sal_Int32 ) +{ + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/AccessibleText.cxx b/sc/source/ui/Accessibility/AccessibleText.cxx new file mode 100644 index 0000000000..3f7ff57f8f --- /dev/null +++ b/sc/source/ui/Accessibility/AccessibleText.cxx @@ -0,0 +1,1386 @@ +/* -*- 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 <scitems.hxx> +#include <editeng/eeitem.hxx> + +#include <memory> +#include <AccessibleText.hxx> +#include <AccessibleCell.hxx> +#include <attrib.hxx> +#include <tabvwsh.hxx> +#include <editutil.hxx> +#include <document.hxx> +#include <scmod.hxx> +#include <prevwsh.hxx> +#include <docsh.hxx> +#include <prevloc.hxx> +#include <patattr.hxx> +#include <inputwin.hxx> +#include <editeng/unofored.hxx> +#include <editeng/editview.hxx> +#include <editeng/unoedhlp.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/justifyitem.hxx> +#include <svx/svdmodel.hxx> +#include <svx/algitem.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +class ScViewForwarder : public SvxViewForwarder +{ + ScTabViewShell* mpViewShell; + ScSplitPos meSplitPos; +public: + ScViewForwarder(ScTabViewShell* pViewShell, ScSplitPos eSplitPos); + + virtual bool IsValid() const override; + virtual Point LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const override; + virtual Point PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const override; + + void SetInvalid(); +}; + +ScViewForwarder::ScViewForwarder(ScTabViewShell* pViewShell, ScSplitPos eSplitPos) + : + mpViewShell(pViewShell), + meSplitPos(eSplitPos) +{ +} + +bool ScViewForwarder::IsValid() const +{ + return mpViewShell != nullptr; +} + +Point ScViewForwarder::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindowByPos(meSplitPos); + if (pWindow) + return pWindow->LogicToPixel( rPoint, rMapMode ); + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return Point(); +} + +Point ScViewForwarder::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindowByPos(meSplitPos); + if (pWindow) + return pWindow->PixelToLogic( rPoint, rMapMode ); + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return Point(); +} + +void ScViewForwarder::SetInvalid() +{ + mpViewShell = nullptr; +} + +class ScEditObjectViewForwarder : public SvxViewForwarder +{ + VclPtr<OutputDevice> mpWindow; + // #i49561# EditView needed for access to its visible area. + const EditView* mpEditView; +public: + ScEditObjectViewForwarder( OutputDevice* pWindow, + const EditView* _pEditView); + + virtual bool IsValid() const override; + virtual Point LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const override; + virtual Point PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const override; + + void SetInvalid(); +}; + +ScEditObjectViewForwarder::ScEditObjectViewForwarder( OutputDevice* pWindow, + const EditView* _pEditView ) + : mpWindow(pWindow) + , mpEditView( _pEditView ) +{ +} + +bool ScEditObjectViewForwarder::IsValid() const +{ + return (mpWindow != nullptr); +} + +Point ScEditObjectViewForwarder::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + if (mpWindow) + { + // #i49561# - consider offset of the visible area + // of the EditView before converting point to pixel. + Point aPoint( rPoint ); + if ( mpEditView ) + { + tools::Rectangle aEditViewVisArea( mpEditView->GetVisArea() ); + aPoint += aEditViewVisArea.TopLeft(); + } + return mpWindow->LogicToPixel( aPoint, rMapMode ); + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return Point(); +} + +Point ScEditObjectViewForwarder::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + if (mpWindow) + { + // #i49561# - consider offset of the visible area + // of the EditView after converting point to logic. + Point aPoint( mpWindow->PixelToLogic( rPoint, rMapMode ) ); + if ( mpEditView ) + { + tools::Rectangle aEditViewVisArea( mpEditView->GetVisArea() ); + aPoint -= aEditViewVisArea.TopLeft(); + } + return aPoint; + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return Point(); +} + +void ScEditObjectViewForwarder::SetInvalid() +{ + mpWindow = nullptr; +} + +class ScPreviewViewForwarder : public SvxViewForwarder +{ +protected: + ScPreviewShell* mpViewShell; +public: + explicit ScPreviewViewForwarder(ScPreviewShell* pViewShell); + + virtual bool IsValid() const override; + virtual Point LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const override; + virtual Point PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const override; + + void SetInvalid(); +}; + +ScPreviewViewForwarder::ScPreviewViewForwarder(ScPreviewShell* pViewShell) + : mpViewShell(pViewShell) +{ +} + +bool ScPreviewViewForwarder::IsValid() const +{ + return mpViewShell != nullptr; +} + +Point ScPreviewViewForwarder::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindow(); + if (pWindow) + { + MapMode aMapMode(pWindow->GetMapMode().GetMapUnit()); + Point aPoint2( OutputDevice::LogicToLogic( rPoint, rMapMode, aMapMode) ); + return pWindow->LogicToPixel(aPoint2); + } + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return Point(); +} + +Point ScPreviewViewForwarder::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + if (mpViewShell) + { + vcl::Window* pWindow = mpViewShell->GetWindow(); + if (pWindow) + { + MapMode aMapMode(pWindow->GetMapMode()); + aMapMode.SetOrigin(Point()); + Point aPoint1( pWindow->PixelToLogic( rPoint ) ); + Point aPoint2( OutputDevice::LogicToLogic( aPoint1, + MapMode(aMapMode.GetMapUnit()), + rMapMode ) ); + return aPoint2; + } + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return Point(); +} + +void ScPreviewViewForwarder::SetInvalid() +{ + mpViewShell = nullptr; +} + +namespace { + +class ScPreviewHeaderFooterViewForwarder : public ScPreviewViewForwarder +{ +public: + ScPreviewHeaderFooterViewForwarder(ScPreviewShell* pViewShell); +}; + +} + +ScPreviewHeaderFooterViewForwarder::ScPreviewHeaderFooterViewForwarder(ScPreviewShell* pViewShell) + : + ScPreviewViewForwarder(pViewShell) +{ +} + +namespace { + +class ScPreviewCellViewForwarder : public ScPreviewViewForwarder +{ +public: + ScPreviewCellViewForwarder(ScPreviewShell* pViewShell); +}; + +} + +ScPreviewCellViewForwarder::ScPreviewCellViewForwarder(ScPreviewShell* pViewShell) + : + ScPreviewViewForwarder(pViewShell) +{ +} + +namespace { + +class ScPreviewHeaderCellViewForwarder : public ScPreviewViewForwarder +{ +public: + ScPreviewHeaderCellViewForwarder(ScPreviewShell* pViewShell); +}; + +} + +ScPreviewHeaderCellViewForwarder::ScPreviewHeaderCellViewForwarder(ScPreviewShell* pViewShell) + : + ScPreviewViewForwarder(pViewShell) +{ +} + +namespace { + +class ScPreviewNoteViewForwarder : public ScPreviewViewForwarder +{ +public: + ScPreviewNoteViewForwarder(ScPreviewShell* pViewShell); +}; + +} + +ScPreviewNoteViewForwarder::ScPreviewNoteViewForwarder(ScPreviewShell* pViewShell) + : + ScPreviewViewForwarder(pViewShell) +{ +} + +class ScEditViewForwarder : public SvxEditViewForwarder +{ + EditView* mpEditView; + VclPtr<OutputDevice> mpWindow; +public: + ScEditViewForwarder(EditView* pEditView, OutputDevice* pWin); + + virtual bool IsValid() const override; + virtual Point LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const override; + virtual Point PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const override; + virtual bool GetSelection( ESelection& rSelection ) const override; + virtual bool SetSelection( const ESelection& rSelection ) override; + virtual bool Copy() override; + virtual bool Cut() override; + virtual bool Paste() override; + + void SetInvalid(); +}; + +ScEditViewForwarder::ScEditViewForwarder(EditView* pEditView, OutputDevice* pWin) + : mpEditView(pEditView) + , mpWindow(pWin) +{ +} + +bool ScEditViewForwarder::IsValid() const +{ + return mpWindow && mpEditView; +} + +Point ScEditViewForwarder::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + if (mpWindow) + return mpWindow->LogicToPixel( rPoint, rMapMode ); + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return Point(); +} + +Point ScEditViewForwarder::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + if (mpWindow) + return mpWindow->PixelToLogic( rPoint, rMapMode ); + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return Point(); +} + +bool ScEditViewForwarder::GetSelection( ESelection& rSelection ) const +{ + bool bResult(false); + if (IsValid()) + { + rSelection = mpEditView->GetSelection(); + bResult = true; + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return bResult; +} + +bool ScEditViewForwarder::SetSelection( const ESelection& rSelection ) +{ + bool bResult(false); + if (IsValid()) + { + mpEditView->SetSelection(rSelection); + bResult = true; + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return bResult; +} + +bool ScEditViewForwarder::Copy() +{ + bool bResult(false); + if (IsValid()) + { + mpEditView->Copy(); + bResult = true; + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return bResult; +} + +bool ScEditViewForwarder::Cut() +{ + bool bResult(false); + if (IsValid()) + { + mpEditView->Cut(); + bResult = true; + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return bResult; +} + +bool ScEditViewForwarder::Paste() +{ + bool bResult(false); + if (IsValid()) + { + mpEditView->Paste(); + bResult = true; + } + else + { + OSL_FAIL("this ViewForwarder is not valid"); + } + return bResult; +} + +void ScEditViewForwarder::SetInvalid() +{ + mpWindow = nullptr; + mpEditView = nullptr; +} + +// ScAccessibleCellTextData: shared data between sub objects of an accessible cell text object + +ScAccessibleCellTextData::ScAccessibleCellTextData(ScTabViewShell* pViewShell, + const ScAddress& rP, ScSplitPos eSplitPos, ScAccessibleCell* pAccCell) + : ScAccessibleCellBaseTextData(GetDocShell(pViewShell), rP), + mpViewShell(pViewShell), + meSplitPos(eSplitPos), + mpAccessibleCell( pAccCell ) +{ +} + +ScAccessibleCellTextData::~ScAccessibleCellTextData() +{ + if (pEditEngine) + pEditEngine->SetNotifyHdl(Link<EENotify&,void>()); + mpViewForwarder.reset(); +} + +void ScAccessibleCellTextData::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying ) + { + mpViewShell = nullptr; // invalid now + if (mpViewForwarder) + mpViewForwarder->SetInvalid(); + } + ScAccessibleCellBaseTextData::Notify(rBC, rHint); +} + +ScAccessibleTextData* ScAccessibleCellTextData::Clone() const +{ + return new ScAccessibleCellTextData( mpViewShell, aCellPos, meSplitPos, mpAccessibleCell ); +} + +SvxTextForwarder* ScAccessibleCellTextData::GetTextForwarder() +{ + ScCellTextData::GetTextForwarder(); // creates Forwarder and EditEngine + + if ( pDocShell && pEditEngine && mpViewShell ) + { + ScDocument& rDoc = pDocShell->GetDocument(); + tools::Long nSizeX, nSizeY; + mpViewShell->GetViewData().GetMergeSizePixel( + aCellPos.Col(), aCellPos.Row(), nSizeX, nSizeY); + + Size aSize(nSizeX, nSizeY); + + // #i92143# text getRangeExtents reports incorrect 'x' values for spreadsheet cells + tools::Long nIndent = 0; + const SvxHorJustifyItem* pHorJustifyItem = rDoc.GetAttr( aCellPos, ATTR_HOR_JUSTIFY ); + SvxCellHorJustify eHorJust = pHorJustifyItem ? pHorJustifyItem->GetValue() : SvxCellHorJustify::Standard; + if ( eHorJust == SvxCellHorJustify::Left ) + { + const ScIndentItem* pIndentItem = rDoc.GetAttr( aCellPos, ATTR_INDENT ); + if ( pIndentItem ) + { + nIndent = static_cast< tools::Long >( pIndentItem->GetValue() ); + } + } + + const SvxMarginItem* pMarginItem = rDoc.GetAttr( aCellPos, ATTR_MARGIN ); + ScViewData& rViewData = mpViewShell->GetViewData(); + double nPPTX = rViewData.GetPPTX(); + double nPPTY = rViewData.GetPPTY(); + tools::Long nLeftM = ( pMarginItem ? static_cast< tools::Long >( ( pMarginItem->GetLeftMargin() + nIndent ) * nPPTX ) : 0 ); + tools::Long nTopM = ( pMarginItem ? static_cast< tools::Long >( pMarginItem->GetTopMargin() * nPPTY ) : 0 ); + tools::Long nRightM = ( pMarginItem ? static_cast< tools::Long >( pMarginItem->GetRightMargin() * nPPTX ) : 0 ); + tools::Long nBottomM = ( pMarginItem ? static_cast< tools::Long >( pMarginItem->GetBottomMargin() * nPPTY ) : 0 ); + tools::Long nWidth = aSize.getWidth() - nLeftM - nRightM; + aSize.setWidth( nWidth ); + aSize.setHeight( aSize.getHeight() - nTopM - nBottomM ); + + vcl::Window* pWin = mpViewShell->GetWindowByPos( meSplitPos ); + if ( pWin ) + { + aSize = pWin->PixelToLogic( aSize, pEditEngine->GetRefMapMode() ); + } + + /* #i19430# Gnopernicus reads text partly if it sticks out of the cell + boundaries. This leads to wrong results in cases where the cell text + is rotated, because rotation is not taken into account when calcu- + lating the visible part of the text. In these cases we will expand + the cell size passed as paper size to the edit engine. The function + accessibility::AccessibleStaticTextBase::GetParagraphBoundingBox() + (see svx/source/accessibility/AccessibleStaticTextBase.cxx) will + return the size of the complete text then, which is used to expand + the cell bounding box in ScAccessibleCell::GetBoundingBox() + (see sc/source/ui/Accessibility/AccessibleCell.cxx). */ + const ScRotateValueItem* pItem = rDoc.GetAttr( aCellPos, ATTR_ROTATE_VALUE ); + if( pItem && (pItem->GetValue() != 0_deg100) ) + { + pEditEngine->SetPaperSize( Size( LONG_MAX, aSize.getHeight() ) ); + tools::Long nTxtWidth = static_cast< tools::Long >( pEditEngine->CalcTextWidth() ); + aSize.setWidth( std::max( aSize.getWidth(), nTxtWidth + 2 ) ); + } + else + { + // #i92143# text getRangeExtents reports incorrect 'x' values for spreadsheet cells + const ScLineBreakCell* pLineBreakItem = rDoc.GetAttr( aCellPos, ATTR_LINEBREAK ); + bool bLineBreak = ( pLineBreakItem && pLineBreakItem->GetValue() ); + if ( !bLineBreak ) + { + tools::Long nTxtWidth = static_cast< tools::Long >( pEditEngine->CalcTextWidth() ); + aSize.setWidth( ::std::max( aSize.getWidth(), nTxtWidth ) ); + } + } + + pEditEngine->SetPaperSize( aSize ); + + // #i92143# text getRangeExtents reports incorrect 'x' values for spreadsheet cells + if ( eHorJust == SvxCellHorJustify::Standard && rDoc.HasValueData( aCellPos.Col(), aCellPos.Row(), aCellPos.Tab() ) ) + { + pEditEngine->SetDefaultItem( SvxAdjustItem( SvxAdjust::Right, EE_PARA_JUST ) ); + } + + Size aTextSize; + if ( pWin ) + { + aTextSize = pWin->LogicToPixel( Size( pEditEngine->CalcTextWidth(), pEditEngine->GetTextHeight() ), pEditEngine->GetRefMapMode() ); + } + tools::Long nTextWidth = aTextSize.Width(); + tools::Long nTextHeight = aTextSize.Height(); + + tools::Long nOffsetX = nLeftM; + tools::Long nDiffX = nTextWidth - nWidth; + if ( nDiffX > 0 ) + { + switch ( eHorJust ) + { + case SvxCellHorJustify::Right: + { + nOffsetX -= nDiffX; + } + break; + case SvxCellHorJustify::Center: + { + nOffsetX -= nDiffX / 2; + } + break; + default: + { + } + break; + } + } + + tools::Long nOffsetY = 0; + const SvxVerJustifyItem* pVerJustifyItem = rDoc.GetAttr( aCellPos, ATTR_VER_JUSTIFY ); + SvxCellVerJustify eVerJust = ( pVerJustifyItem ? pVerJustifyItem->GetValue() : SvxCellVerJustify::Standard ); + switch ( eVerJust ) + { + case SvxCellVerJustify::Standard: + case SvxCellVerJustify::Bottom: + { + nOffsetY = nSizeY - nBottomM - nTextHeight; + } + break; + case SvxCellVerJustify::Center: + { + nOffsetY = ( nSizeY - nTopM - nBottomM - nTextHeight ) / 2 + nTopM; + } + break; + default: + { + nOffsetY = nTopM; + } + break; + } + + if ( mpAccessibleCell ) + { + mpAccessibleCell->SetOffset( Point( nOffsetX, nOffsetY ) ); + } + + pEditEngine->SetNotifyHdl( LINK(this, ScAccessibleCellTextData, NotifyHdl) ); + } + + return pForwarder.get(); +} + +SvxViewForwarder* ScAccessibleCellTextData::GetViewForwarder() +{ + if (!mpViewForwarder) + mpViewForwarder.reset(new ScViewForwarder(mpViewShell, meSplitPos)); + return mpViewForwarder.get(); +} + +SvxEditViewForwarder* ScAccessibleCellTextData::GetEditViewForwarder( bool /* bCreate */ ) +{ + //#102219#; there should no EditViewForwarder be, because the cell is now readonly in this interface + return nullptr; +} + +IMPL_LINK(ScAccessibleTextData, NotifyHdl, EENotify&, aNotify, void) +{ + ::std::unique_ptr< SfxHint > aHint = SvxEditSourceHelper::EENotification2Hint( &aNotify ); + + if (aHint) + GetBroadcaster().Broadcast(*aHint); +} + +ScDocShell* ScAccessibleCellTextData::GetDocShell(ScTabViewShell* pViewShell) +{ + ScDocShell* pDocSh = nullptr; + if (pViewShell) + pDocSh = pViewShell->GetViewData().GetDocShell(); + return pDocSh; +} + +ScAccessibleEditObjectTextData::ScAccessibleEditObjectTextData(EditView* pEditView, OutputDevice* pWin, bool isClone) + : + mpEditView(pEditView), + mpEditEngine(pEditView ? pEditView->GetEditEngine() : nullptr), + mpWindow(pWin) +{ + // If the object is cloned, do NOT add notify hdl. + mbIsCloned = isClone; + if (mpEditEngine && !mbIsCloned) + mpEditEngine->SetNotifyHdl( LINK(this, ScAccessibleEditObjectTextData, NotifyHdl) ); +} + +ScAccessibleEditObjectTextData::~ScAccessibleEditObjectTextData() +{ + // If the object is cloned, do NOT set notify hdl. + if (mpEditEngine && !mbIsCloned) + mpEditEngine->SetNotifyHdl(Link<EENotify&,void>()); + mpViewForwarder.reset(); + mpEditViewForwarder.reset(); + mpForwarder.reset(); +} + +void ScAccessibleEditObjectTextData::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying ) + { + mpWindow = nullptr; + mpEditView = nullptr; + mpEditEngine = nullptr; + mpForwarder.reset(); + if (mpViewForwarder) + mpViewForwarder->SetInvalid(); + if (mpEditViewForwarder) + mpEditViewForwarder->SetInvalid(); + } + ScAccessibleTextData::Notify(rBC, rHint); +} + +ScAccessibleTextData* ScAccessibleEditObjectTextData::Clone() const +{ + // Add para to indicate the object is cloned + return new ScAccessibleEditObjectTextData(mpEditView, mpWindow, true); +} + +SvxTextForwarder* ScAccessibleEditObjectTextData::GetTextForwarder() +{ + if ((!mpForwarder && mpEditView) || (mpEditEngine && !mpEditEngine->GetNotifyHdl().IsSet())) + { + if (!mpEditEngine) + mpEditEngine = mpEditView->GetEditEngine(); + // If the object is cloned, do NOT add notify hdl. + if (mpEditEngine && !mpEditEngine->GetNotifyHdl().IsSet()&&!mbIsCloned) + mpEditEngine->SetNotifyHdl( LINK(this, ScAccessibleEditObjectTextData, NotifyHdl) ); + if(!mpForwarder) + mpForwarder.reset(new SvxEditEngineForwarder(*mpEditEngine)); + } + return mpForwarder.get(); +} + +SvxViewForwarder* ScAccessibleEditObjectTextData::GetViewForwarder() +{ + if (!mpViewForwarder) + { + // i#49561 Get right-aligned cell content to be read by screenreader. + mpViewForwarder.reset(new ScEditObjectViewForwarder( mpWindow, mpEditView )); + } + return mpViewForwarder.get(); +} + +SvxEditViewForwarder* ScAccessibleEditObjectTextData::GetEditViewForwarder( bool bCreate ) +{ + if (!mpEditViewForwarder && mpEditView) + mpEditViewForwarder.reset(new ScEditViewForwarder(mpEditView, mpWindow)); + if (bCreate) + { + if (!mpEditView && mpEditViewForwarder) + { + mpEditViewForwarder.reset(); + } + } + return mpEditViewForwarder.get(); +} + +IMPL_LINK(ScAccessibleEditObjectTextData, NotifyHdl, EENotify&, rNotify, void) +{ + ::std::unique_ptr< SfxHint > aHint = SvxEditSourceHelper::EENotification2Hint( &rNotify ); + + if (aHint) + GetBroadcaster().Broadcast(*aHint); +} + +ScAccessibleEditLineTextData::ScAccessibleEditLineTextData(EditView* pEditView, + OutputDevice* pWin, + ScTextWnd* pTxtWnd) + : ScAccessibleEditObjectTextData(pEditView, pWin) + , mpTxtWnd(pTxtWnd) + , mbEditEngineCreated(false) +{ + if (mpTxtWnd) + mpTxtWnd->InsertAccessibleTextData( *this ); +} + +ScAccessibleEditLineTextData::~ScAccessibleEditLineTextData() +{ + if (mpTxtWnd) + mpTxtWnd->RemoveAccessibleTextData( *this ); + + if (mbEditEngineCreated && mpEditEngine) + { + delete mpEditEngine; + mpEditEngine = nullptr; // don't access in ScAccessibleEditObjectTextData dtor! + } + else if (mpTxtWnd && mpTxtWnd->HasEditView() && mpTxtWnd->GetEditView()->GetEditEngine()) + { + // the NotifyHdl also has to be removed from the ScTextWnd's EditEngine + // (it's set in ScAccessibleEditLineTextData::GetTextForwarder, and mpEditEngine + // is reset there) + mpTxtWnd->GetEditView()->GetEditEngine()->SetNotifyHdl(Link<EENotify&,void>()); + } +} + +void ScAccessibleEditLineTextData::Dispose() +{ + if (mpTxtWnd) + mpTxtWnd->RemoveAccessibleTextData( *this ); + + ResetEditMode(); + mpWindow = nullptr; + mpTxtWnd = nullptr; +} + +ScAccessibleTextData* ScAccessibleEditLineTextData::Clone() const +{ + return new ScAccessibleEditLineTextData(mpEditView, mpWindow, mpTxtWnd); +} + +SvxTextForwarder* ScAccessibleEditLineTextData::GetTextForwarder() +{ + if (mpTxtWnd) + { + if (mpTxtWnd->HasEditView()) + { + mpEditView = mpTxtWnd->GetEditView(); + + if (mbEditEngineCreated && mpEditEngine) + ResetEditMode(); + mbEditEngineCreated = false; + + mpEditView = mpTxtWnd->GetEditView(); + ScAccessibleEditObjectTextData::GetTextForwarder(); // fill the mpForwarder + mpEditEngine = nullptr; + } + else + { + mpEditView = nullptr; + + if (mpEditEngine && !mbEditEngineCreated) + ResetEditMode(); + if (!mpEditEngine) + { + rtl::Reference<SfxItemPool> pEnginePool = EditEngine::CreatePool(); + pEnginePool->FreezeIdRanges(); + mpEditEngine = new ScFieldEditEngine(nullptr, pEnginePool.get(), nullptr, true); + mbEditEngineCreated = true; + mpEditEngine->EnableUndo( false ); + mpEditEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM)); + mpForwarder.reset(new SvxEditEngineForwarder(*mpEditEngine)); + + mpEditEngine->SetText(mpTxtWnd->GetTextString()); + +#if 0 + Size aSize(pTxtWnd->GetSizePixel()); + aSize = pTxtWnd->PixelToLogic(aSize, mpEditEngine->GetRefMapMode()); + mpEditEngine->SetPaperSize(aSize); +#else + OutputDevice& rDevice = mpTxtWnd->GetDrawingArea()->get_ref_device(); + Size aSize(rDevice.GetOutputSizePixel()); + aSize = rDevice.PixelToLogic(aSize, mpEditEngine->GetRefMapMode()); + mpEditEngine->SetPaperSize(aSize); +#endif + + mpEditEngine->SetNotifyHdl( LINK(this, ScAccessibleEditObjectTextData, NotifyHdl) ); + } + } + } + return mpForwarder.get(); +} + +SvxEditViewForwarder* ScAccessibleEditLineTextData::GetEditViewForwarder( bool bCreate ) +{ + if (mpTxtWnd) + { + if (!mpTxtWnd->HasEditView() && bCreate) + { + if ( !mpTxtWnd->IsInputActive() ) + { + mpTxtWnd->StartEditEngine(); + mpTxtWnd->GrabFocus(); + + mpEditView = mpTxtWnd->GetEditView(); + } + } + } + + return ScAccessibleEditObjectTextData::GetEditViewForwarder(bCreate); +} + +void ScAccessibleEditLineTextData::ResetEditMode() +{ + if (mbEditEngineCreated && mpEditEngine) + delete mpEditEngine; + else if (mpTxtWnd && mpTxtWnd->HasEditView() && mpTxtWnd->GetEditView()->GetEditEngine()) + mpTxtWnd->GetEditView()->GetEditEngine()->SetNotifyHdl(Link<EENotify&,void>()); + mpEditEngine = nullptr; + + mpForwarder.reset(); + mpEditViewForwarder.reset(); + mpViewForwarder.reset(); + mbEditEngineCreated = false; +} + +void ScAccessibleEditLineTextData::TextChanged() +{ + if (mbEditEngineCreated && mpEditEngine) + { + if (mpTxtWnd) + mpEditEngine->SetText(mpTxtWnd->GetTextString()); + } +} + +void ScAccessibleEditLineTextData::StartEdit() +{ + ResetEditMode(); + mpEditView = nullptr; + + // send SdrHintKind::BeginEdit + SdrHint aHint(SdrHintKind::BeginEdit); + GetBroadcaster().Broadcast( aHint ); +} + +void ScAccessibleEditLineTextData::EndEdit() +{ + // send SdrHintKind::EndEdit + SdrHint aHint(SdrHintKind::EndEdit); + GetBroadcaster().Broadcast( aHint ); + + ResetEditMode(); + mpEditView = nullptr; +} + +// ScAccessiblePreviewCellTextData: shared data between sub objects of an accessible cell text object + +ScAccessiblePreviewCellTextData::ScAccessiblePreviewCellTextData(ScPreviewShell* pViewShell, + const ScAddress& rP) + : ScAccessibleCellBaseTextData(GetDocShell(pViewShell), rP), + mpViewShell(pViewShell) +{ +} + +ScAccessiblePreviewCellTextData::~ScAccessiblePreviewCellTextData() +{ + if (pEditEngine) + pEditEngine->SetNotifyHdl(Link<EENotify&,void>()); + mpViewForwarder.reset(); +} + +void ScAccessiblePreviewCellTextData::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying ) + { + mpViewShell = nullptr; // invalid now + if (mpViewForwarder) + mpViewForwarder->SetInvalid(); + } + ScAccessibleCellBaseTextData::Notify(rBC, rHint); +} + +ScAccessibleTextData* ScAccessiblePreviewCellTextData::Clone() const +{ + return new ScAccessiblePreviewCellTextData(mpViewShell, aCellPos); +} + +SvxTextForwarder* ScAccessiblePreviewCellTextData::GetTextForwarder() +{ + bool bEditEngineBefore(pEditEngine != nullptr); + + ScCellTextData::GetTextForwarder(); // creates Forwarder and EditEngine + + if (!bEditEngineBefore && pEditEngine) + { + Size aSize(mpViewShell->GetLocationData().GetCellOutputRect(aCellPos).GetSize()); + vcl::Window* pWin = mpViewShell->GetWindow(); + if (pWin) + aSize = pWin->PixelToLogic(aSize, pEditEngine->GetRefMapMode()); + pEditEngine->SetPaperSize(aSize); + } + + if (pEditEngine) + pEditEngine->SetNotifyHdl( LINK(this, ScAccessiblePreviewCellTextData, NotifyHdl) ); + + return pForwarder.get(); +} + +SvxViewForwarder* ScAccessiblePreviewCellTextData::GetViewForwarder() +{ + if (!mpViewForwarder) + mpViewForwarder.reset(new ScPreviewCellViewForwarder(mpViewShell)); + return mpViewForwarder.get(); +} + +ScDocShell* ScAccessiblePreviewCellTextData::GetDocShell(ScPreviewShell* pViewShell) +{ + ScDocShell* pDocSh = nullptr; + if (pViewShell) + pDocSh = pViewShell->GetDocument().GetDocumentShell(); + return pDocSh; +} + +// ScAccessiblePreviewHeaderCellTextData: shared data between sub objects of an accessible cell text object + +ScAccessiblePreviewHeaderCellTextData::ScAccessiblePreviewHeaderCellTextData(ScPreviewShell* pViewShell, + OUString aText, const ScAddress& rP, bool bColHeader, bool bRowHeader) + : ScAccessibleCellBaseTextData(GetDocShell(pViewShell), rP), + mpViewShell(pViewShell), + maText(std::move(aText)), + mbColHeader(bColHeader), + mbRowHeader(bRowHeader) +{ +} + +ScAccessiblePreviewHeaderCellTextData::~ScAccessiblePreviewHeaderCellTextData() +{ + if (pEditEngine) + pEditEngine->SetNotifyHdl(Link<EENotify&,void>()); + mpViewForwarder.reset(); +} + +void ScAccessiblePreviewHeaderCellTextData::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying ) + { + mpViewShell = nullptr; // invalid now + if (mpViewForwarder) + mpViewForwarder->SetInvalid(); + } + ScAccessibleCellBaseTextData::Notify(rBC, rHint); +} + +ScAccessibleTextData* ScAccessiblePreviewHeaderCellTextData::Clone() const +{ + return new ScAccessiblePreviewHeaderCellTextData(mpViewShell, maText, aCellPos, mbColHeader, mbRowHeader); +} + +SvxTextForwarder* ScAccessiblePreviewHeaderCellTextData::GetTextForwarder() +{ + if (!pEditEngine) + { + if ( pDocShell ) + { + ScDocument& rDoc = pDocShell->GetDocument(); + pEditEngine = rDoc.CreateFieldEditEngine(); + } + else + { + rtl::Reference<SfxItemPool> pEnginePool = EditEngine::CreatePool(); + pEnginePool->FreezeIdRanges(); + pEditEngine.reset( new ScFieldEditEngine(nullptr, pEnginePool.get(), nullptr, true) ); + } + pEditEngine->EnableUndo( false ); + if (pDocShell) + pEditEngine->SetRefDevice(pDocShell->GetRefDevice()); + else + pEditEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM)); + pForwarder.reset( new SvxEditEngineForwarder(*pEditEngine) ); + } + + if (bDataValid) + return pForwarder.get(); + + if (!maText.isEmpty()) + { + if ( mpViewShell ) + { + Size aOutputSize; + vcl::Window* pWindow = mpViewShell->GetWindow(); + if ( pWindow ) + aOutputSize = pWindow->GetOutputSizePixel(); + tools::Rectangle aVisRect( Point(), aOutputSize ); + Size aSize(mpViewShell->GetLocationData().GetHeaderCellOutputRect(aVisRect, aCellPos, mbColHeader).GetSize()); + if (pWindow) + aSize = pWindow->PixelToLogic(aSize, pEditEngine->GetRefMapMode()); + pEditEngine->SetPaperSize(aSize); + } + pEditEngine->SetTextCurrentDefaults( maText ); + } + + bDataValid = true; + + pEditEngine->SetNotifyHdl( LINK(this, ScAccessiblePreviewHeaderCellTextData, NotifyHdl) ); + + return pForwarder.get(); +} + +SvxViewForwarder* ScAccessiblePreviewHeaderCellTextData::GetViewForwarder() +{ + if (!mpViewForwarder) + mpViewForwarder.reset(new ScPreviewHeaderCellViewForwarder(mpViewShell)); + return mpViewForwarder.get(); +} + +ScDocShell* ScAccessiblePreviewHeaderCellTextData::GetDocShell(ScPreviewShell* pViewShell) +{ + ScDocShell* pDocSh = nullptr; + if (pViewShell) + pDocSh = pViewShell->GetDocument().GetDocumentShell(); + return pDocSh; +} + +ScAccessibleHeaderTextData::ScAccessibleHeaderTextData(ScPreviewShell* pViewShell, + const EditTextObject* pEditObj, SvxAdjust eAdjust) + : + mpViewShell(pViewShell), + mpDocSh(nullptr), + mpEditObj(pEditObj), + mbDataValid(false), + meAdjust(eAdjust) +{ + if (pViewShell) + mpDocSh = pViewShell->GetDocument().GetDocumentShell(); + if (mpDocSh) + mpDocSh->GetDocument().AddUnoObject(*this); +} + +ScAccessibleHeaderTextData::~ScAccessibleHeaderTextData() +{ + SolarMutexGuard aGuard; // needed for EditEngine dtor + + if (mpDocSh) + mpDocSh->GetDocument().RemoveUnoObject(*this); + if (mpEditEngine) + mpEditEngine->SetNotifyHdl(Link<EENotify&,void>()); + mpEditEngine.reset(); + mpForwarder.reset(); +} + +ScAccessibleTextData* ScAccessibleHeaderTextData::Clone() const +{ + return new ScAccessibleHeaderTextData(mpViewShell, mpEditObj, meAdjust); +} + +void ScAccessibleHeaderTextData::Notify( SfxBroadcaster&, const SfxHint& rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying ) + { + mpViewShell = nullptr;// invalid now + mpDocSh = nullptr; + if (mxViewForwarder) + mxViewForwarder->SetInvalid(); + } +} + +SvxTextForwarder* ScAccessibleHeaderTextData::GetTextForwarder() +{ + if (!mpEditEngine) + { + rtl::Reference<SfxItemPool> pEnginePool = EditEngine::CreatePool(); + pEnginePool->FreezeIdRanges(); + std::unique_ptr<ScHeaderEditEngine> pHdrEngine(new ScHeaderEditEngine( pEnginePool.get() )); + + pHdrEngine->EnableUndo( false ); + pHdrEngine->SetRefMapMode(MapMode(MapUnit::MapTwip)); + + // default font must be set, independently of document + // -> use global pool from module + + SfxItemSet aDefaults( pHdrEngine->GetEmptyItemSet() ); + const ScPatternAttr& rPattern = SC_MOD()->GetPool().GetDefaultItem(ATTR_PATTERN); + rPattern.FillEditItemSet( &aDefaults ); + // FillEditItemSet adjusts font height to 1/100th mm, + // but for header/footer twips is needed, as in the PatternAttr: + aDefaults.Put( rPattern.GetItem(ATTR_FONT_HEIGHT).CloneSetWhich(EE_CHAR_FONTHEIGHT) ); + aDefaults.Put( rPattern.GetItem(ATTR_CJK_FONT_HEIGHT).CloneSetWhich(EE_CHAR_FONTHEIGHT_CJK) ); + aDefaults.Put( rPattern.GetItem(ATTR_CTL_FONT_HEIGHT).CloneSetWhich(EE_CHAR_FONTHEIGHT_CTL) ); + aDefaults.Put( SvxAdjustItem( meAdjust, EE_PARA_JUST ) ); + pHdrEngine->SetDefaults( aDefaults ); + + ScHeaderFieldData aData; + if (mpViewShell) + mpViewShell->FillFieldData(aData); + else + ScHeaderFooterTextObj::FillDummyFieldData( aData ); + pHdrEngine->SetData( aData ); + + mpEditEngine = std::move(pHdrEngine); + mpForwarder.reset(new SvxEditEngineForwarder(*mpEditEngine)); + } + + if (mbDataValid) + return mpForwarder.get(); + + if ( mpViewShell ) + { + tools::Rectangle aVisRect; + mpViewShell->GetLocationData().GetHeaderPosition(aVisRect); + Size aSize(aVisRect.GetSize()); + vcl::Window* pWin = mpViewShell->GetWindow(); + if (pWin) + aSize = pWin->PixelToLogic(aSize, mpEditEngine->GetRefMapMode()); + mpEditEngine->SetPaperSize(aSize); + } + if (mpEditObj) + mpEditEngine->SetTextCurrentDefaults(*mpEditObj); + + mbDataValid = true; + return mpForwarder.get(); +} + +SvxViewForwarder* ScAccessibleHeaderTextData::GetViewForwarder() +{ + if (!mxViewForwarder) + mxViewForwarder = std::make_unique<ScPreviewHeaderFooterViewForwarder>(mpViewShell); + return mxViewForwarder.get(); +} + +ScAccessibleNoteTextData::ScAccessibleNoteTextData(ScPreviewShell* pViewShell, + OUString sText, const ScAddress& aCellPos, bool bMarkNote) + : + mpViewShell(pViewShell), + mpDocSh(nullptr), + msText(std::move(sText)), + maCellPos(aCellPos), + mbMarkNote(bMarkNote), + mbDataValid(false) +{ + if (pViewShell) + mpDocSh = pViewShell->GetDocument().GetDocumentShell(); + if (mpDocSh) + mpDocSh->GetDocument().AddUnoObject(*this); +} + +ScAccessibleNoteTextData::~ScAccessibleNoteTextData() +{ + SolarMutexGuard aGuard; // needed for EditEngine dtor + + if (mpDocSh) + mpDocSh->GetDocument().RemoveUnoObject(*this); + if (mpEditEngine) + mpEditEngine->SetNotifyHdl(Link<EENotify&,void>()); + mpEditEngine.reset(); + mpForwarder.reset(); +} + +ScAccessibleTextData* ScAccessibleNoteTextData::Clone() const +{ + return new ScAccessibleNoteTextData(mpViewShell, msText, maCellPos, mbMarkNote); +} + +void ScAccessibleNoteTextData::Notify( SfxBroadcaster&, const SfxHint& rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying ) + { + mpViewShell = nullptr;// invalid now + mpDocSh = nullptr; + if (mxViewForwarder) + mxViewForwarder->SetInvalid(); + } +} + +SvxTextForwarder* ScAccessibleNoteTextData::GetTextForwarder() +{ + if (!mpEditEngine) + { + if ( mpDocSh ) + { + ScDocument& rDoc = mpDocSh->GetDocument(); + mpEditEngine = rDoc.CreateFieldEditEngine(); + } + else + { + rtl::Reference<SfxItemPool> pEnginePool = EditEngine::CreatePool(); + pEnginePool->FreezeIdRanges(); + mpEditEngine.reset( new ScFieldEditEngine(nullptr, pEnginePool.get(), nullptr, true) ); + } + mpEditEngine->EnableUndo( false ); + if (mpDocSh) + mpEditEngine->SetRefDevice(mpDocSh->GetRefDevice()); + else + mpEditEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM)); + mpForwarder.reset( new SvxEditEngineForwarder(*mpEditEngine) ); + } + + if (mbDataValid) + return mpForwarder.get(); + + if (!msText.isEmpty()) + { + + if ( mpViewShell ) + { + Size aOutputSize; + vcl::Window* pWindow = mpViewShell->GetWindow(); + if ( pWindow ) + aOutputSize = pWindow->GetOutputSizePixel(); + tools::Rectangle aVisRect( Point(), aOutputSize ); + Size aSize(mpViewShell->GetLocationData().GetNoteInRangeOutputRect(aVisRect, mbMarkNote, maCellPos).GetSize()); + if (pWindow) + aSize = pWindow->PixelToLogic(aSize, mpEditEngine->GetRefMapMode()); + mpEditEngine->SetPaperSize(aSize); + } + mpEditEngine->SetTextCurrentDefaults( msText ); + } + + mbDataValid = true; + + mpEditEngine->SetNotifyHdl( LINK(this, ScAccessibleNoteTextData, NotifyHdl) ); + + return mpForwarder.get(); +} + +SvxViewForwarder* ScAccessibleNoteTextData::GetViewForwarder() +{ + if (!mxViewForwarder) + mxViewForwarder = std::make_unique<ScPreviewNoteViewForwarder>(mpViewShell); + return mxViewForwarder.get(); +} + +// CSV import ================================================================= + +class ScCsvViewForwarder : public SvxViewForwarder +{ + VclPtr<OutputDevice> mpWindow; + +public: + explicit ScCsvViewForwarder( OutputDevice* pWindow ); + + virtual bool IsValid() const override; + virtual Point LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const override; + virtual Point PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const override; + + void SetInvalid(); +}; + +ScCsvViewForwarder::ScCsvViewForwarder( OutputDevice* pWindow ) : + mpWindow( pWindow ) +{ +} + +bool ScCsvViewForwarder::IsValid() const +{ + return mpWindow != nullptr; +} + +Point ScCsvViewForwarder::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + if( !mpWindow ) return Point(); + return mpWindow->LogicToPixel( rPoint, rMapMode ); +} + +Point ScCsvViewForwarder::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + if( !mpWindow ) return Point(); + return mpWindow->PixelToLogic( rPoint, rMapMode ); +} + +void ScCsvViewForwarder::SetInvalid() +{ + mpWindow = nullptr; +} + +ScAccessibleCsvTextData::ScAccessibleCsvTextData( + OutputDevice* pWindow, EditEngine* pEditEngine, + OUString aCellText, const Size& rCellSize ) : + mpWindow( pWindow ), + mpEditEngine( pEditEngine ), + maCellText(std::move( aCellText )), + maCellSize( rCellSize ) +{ +} + +ScAccessibleCsvTextData::~ScAccessibleCsvTextData() +{ +} + +void ScAccessibleCsvTextData::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying ) + { + mpWindow = nullptr; + mpEditEngine = nullptr; + if (mpViewForwarder) + mpViewForwarder->SetInvalid(); + } + ScAccessibleTextData::Notify( rBC, rHint ); +} + +ScAccessibleTextData* ScAccessibleCsvTextData::Clone() const +{ + return new ScAccessibleCsvTextData( mpWindow, mpEditEngine, maCellText, maCellSize ); +} + +SvxTextForwarder* ScAccessibleCsvTextData::GetTextForwarder() +{ + if( mpEditEngine ) + { + mpEditEngine->SetPaperSize( maCellSize ); + mpEditEngine->SetText( maCellText ); + if( !mpTextForwarder ) + mpTextForwarder.reset( new SvxEditEngineForwarder( *mpEditEngine ) ); + } + else + mpTextForwarder.reset(); + return mpTextForwarder.get(); +} + +SvxViewForwarder* ScAccessibleCsvTextData::GetViewForwarder() +{ + if( !mpViewForwarder ) + mpViewForwarder.reset( new ScCsvViewForwarder( mpWindow ) ); + return mpViewForwarder.get(); +} + +SvxEditViewForwarder* ScAccessibleCsvTextData::GetEditViewForwarder( bool /* bCreate */ ) +{ + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/Accessibility/DrawModelBroadcaster.cxx b/sc/source/ui/Accessibility/DrawModelBroadcaster.cxx new file mode 100644 index 0000000000..c181656fe1 --- /dev/null +++ b/sc/source/ui/Accessibility/DrawModelBroadcaster.cxx @@ -0,0 +1,107 @@ +/* -*- 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 <DrawModelBroadcaster.hxx> +#include <svx/svdmodel.hxx> +#include <svx/unomod.hxx> +#include <svx/svdobj.hxx> + +using namespace ::com::sun::star; + +ScDrawModelBroadcaster::ScDrawModelBroadcaster( SdrModel *pDrawModel ) : + mpDrawModel( pDrawModel ) +{ + if (mpDrawModel) + StartListening( *mpDrawModel ); +} + +ScDrawModelBroadcaster::~ScDrawModelBroadcaster() +{ + if (mpDrawModel) + EndListening( *mpDrawModel ); +} + +void SAL_CALL ScDrawModelBroadcaster::addEventListener( const uno::Reference< document::XEventListener >& xListener ) +{ + std::unique_lock aGuard(maListenerMutex); + maEventListeners.addInterface( aGuard, xListener ); +} + +void SAL_CALL ScDrawModelBroadcaster::removeEventListener( const uno::Reference< document::XEventListener >& xListener ) +{ + std::unique_lock aGuard(maListenerMutex); + maEventListeners.removeInterface( aGuard, xListener ); +} + +void SAL_CALL ScDrawModelBroadcaster::addShapeEventListener( + const css::uno::Reference< css::drawing::XShape >& xShape, + const uno::Reference< document::XShapeEventListener >& xListener ) +{ + assert(xShape.is() && "no shape?"); + std::scoped_lock aGuard(maListenerMutex); + auto rv = maShapeListeners.emplace(xShape, xListener); + assert(rv.second && "duplicate listener?"); + (void)rv; +} + +void SAL_CALL ScDrawModelBroadcaster::removeShapeEventListener( + const css::uno::Reference< css::drawing::XShape >& xShape, + const uno::Reference< document::XShapeEventListener >& xListener ) +{ + std::scoped_lock aGuard(maListenerMutex); + auto it = maShapeListeners.find(xShape); + if (it != maShapeListeners.end()) + { + assert(it->second == xListener && "removing wrong listener?"); + (void)xListener; + maShapeListeners.erase(it); + } +} + +void ScDrawModelBroadcaster::Notify( SfxBroadcaster&, + const SfxHint& rHint ) +{ + if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint) + return; + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint); + + document::EventObject aEvent; + if( !SvxUnoDrawMSFactory::createEvent( mpDrawModel, pSdrHint, aEvent ) ) + return; + + std::unique_lock aGuard(maListenerMutex); + maEventListeners.forEach(aGuard, + [&aEvent](const css::uno::Reference<document::XEventListener>& xListener) + { + xListener->notifyEvent(aEvent); + } + ); + + // 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); + auto it = maShapeListeners.find(xShape); + if (it != maShapeListeners.end()) + it->second->notifyShapeEvent(aEvent); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |