1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <string_view>
23 #include <sal/config.h>
25 #include <AccessibleCell.hxx>
26 #include <scitems.hxx>
27 #include <AccessibleText.hxx>
28 #include <AccessibleDocument.hxx>
29 #include <tabvwsh.hxx>
30 #include <comphelper/sequence.hxx>
31 #include <document.hxx>
33 #include <editsrc.hxx>
34 #include <dociter.hxx>
35 #include <markdata.hxx>
36 #include <cellvalue.hxx>
37 #include <formulaiter.hxx>
38 #include <validat.hxx>
40 #include <unotools/accessiblerelationsethelper.hxx>
41 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
42 #include <com/sun/star/accessibility/AccessibleRelationType.hpp>
43 #include <com/sun/star/accessibility/XAccessibleTable.hpp>
44 #include <editeng/brushitem.hxx>
45 #include <vcl/svapp.hxx>
47 #include <AccessibleSpreadsheet.hxx>
49 using namespace ::com::sun::star
;
50 using namespace ::com::sun::star::accessibility
;
52 rtl::Reference
<ScAccessibleCell
> ScAccessibleCell::create(
53 const uno::Reference
<XAccessible
>& rxParent
,
54 ScTabViewShell
* pViewShell
,
55 const ScAddress
& rCellAddress
,
58 ScAccessibleDocument
* pAccDoc
)
60 rtl::Reference
<ScAccessibleCell
> x(new ScAccessibleCell(
61 rxParent
, pViewShell
, rCellAddress
, nIndex
, eSplitPos
, pAccDoc
));
66 ScAccessibleCell::ScAccessibleCell(
67 const uno::Reference
<XAccessible
>& rxParent
,
68 ScTabViewShell
* pViewShell
,
69 const ScAddress
& rCellAddress
,
72 ScAccessibleDocument
* pAccDoc
)
74 ScAccessibleCellBase(rxParent
, GetDocument(pViewShell
), rCellAddress
, nIndex
),
75 ::accessibility::AccessibleStaticTextBase(CreateEditSource(pViewShell
, rCellAddress
, eSplitPos
)),
76 mpViewShell(pViewShell
),
81 pViewShell
->AddAccessibilityObject(*this);
84 ScAccessibleCell::~ScAccessibleCell()
86 if (!ScAccessibleContextBase::IsDefunc() && !rBHelper
.bInDispose
)
88 // increment refcount to prevent double call off dtor
89 osl_atomic_increment( &m_refCount
);
90 // call dispose to inform object which have a weak reference to this object
95 void ScAccessibleCell::Init()
97 ScAccessibleCellBase::Init();
102 void SAL_CALL
ScAccessibleCell::disposing()
104 SolarMutexGuard aGuard
;
105 // dispose in AccessibleStaticTextBase
110 mpViewShell
->RemoveAccessibilityObject(*this);
111 mpViewShell
= nullptr;
115 ScAccessibleCellBase::disposing();
118 //===== XInterface =====================================================
120 IMPLEMENT_FORWARD_XINTERFACE3( ScAccessibleCell
, ScAccessibleCellBase
, AccessibleStaticTextBase
, ScAccessibleCellAttributeImpl
)
122 //===== XTypeProvider ===================================================
124 css::uno::Sequence
< css::uno::Type
> SAL_CALL
ScAccessibleCell::getTypes()
126 return ::comphelper::concatSequences(
127 ScAccessibleCellBase::getTypes(),
128 AccessibleStaticTextBase::getTypes(),
129 ScAccessibleCellAttributeImpl::getTypes()
132 IMPLEMENT_GET_IMPLEMENTATION_ID( ScAccessibleCell
)
134 //===== XAccessibleComponent ============================================
136 uno::Reference
< XAccessible
> SAL_CALL
ScAccessibleCell::getAccessibleAtPoint(
137 const awt::Point
& rPoint
)
139 return AccessibleStaticTextBase::getAccessibleAtPoint(rPoint
);
142 void SAL_CALL
ScAccessibleCell::grabFocus( )
144 SolarMutexGuard aGuard
;
146 if (getAccessibleParent().is() && mpViewShell
)
148 uno::Reference
<XAccessibleComponent
> xAccessibleComponent(getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY
);
149 if (xAccessibleComponent
.is())
151 xAccessibleComponent
->grabFocus();
152 mpViewShell
->SetCursor(maCellAddress
.Col(), maCellAddress
.Row());
157 AbsoluteScreenPixelRectangle
ScAccessibleCell::GetBoundingBoxOnScreen() const
159 AbsoluteScreenPixelRectangle
aCellRect(GetBoundingBox());
162 vcl::Window
* pWindow
= mpViewShell
->GetWindowByPos(meSplitPos
);
165 AbsoluteScreenPixelRectangle aRect
= pWindow
->GetWindowExtentsAbsolute();
166 aCellRect
.Move(aRect
.Left(), aRect
.Top());
172 tools::Rectangle
ScAccessibleCell::GetBoundingBox() const
174 tools::Rectangle aCellRect
;
177 tools::Long nSizeX
, nSizeY
;
178 mpViewShell
->GetViewData().GetMergeSizePixel(
179 maCellAddress
.Col(), maCellAddress
.Row(), nSizeX
, nSizeY
);
180 aCellRect
.SetSize(Size(nSizeX
, nSizeY
));
181 aCellRect
.SetPos(mpViewShell
->GetViewData().GetScrPos(maCellAddress
.Col(), maCellAddress
.Row(), meSplitPos
, true));
183 vcl::Window
* pWindow
= mpViewShell
->GetWindowByPos(meSplitPos
);
186 tools::Rectangle
aRect(pWindow
->GetWindowExtentsRelative(*pWindow
->GetAccessibleParentWindow()));
187 aRect
.Move(-aRect
.Left(), -aRect
.Top());
188 aCellRect
= aRect
.Intersection(aCellRect
);
191 /* #i19430# Gnopernicus reads text partly if it sticks out of the cell
192 boundaries. This leads to wrong results in cases where the cell
193 text is rotated, because rotation is not taken into account when
194 calculating the visible part of the text. In these cases we will
195 simply expand the cell size to the width of the unrotated text. */
198 const ScRotateValueItem
* pItem
= mpDoc
->GetAttr( maCellAddress
, ATTR_ROTATE_VALUE
);
199 if( pItem
&& (pItem
->GetValue() != 0_deg100
) )
201 tools::Rectangle aParaRect
= GetParagraphBoundingBox();
202 if( !aParaRect
.IsEmpty() && (aCellRect
.GetWidth() < aParaRect
.GetWidth()) )
203 aCellRect
.SetSize( Size( aParaRect
.GetWidth(), aCellRect
.GetHeight() ) );
207 if (aCellRect
.IsEmpty())
208 aCellRect
.SetPos(Point(-1, -1));
212 //===== XAccessibleContext ==============================================
215 ScAccessibleCell::getAccessibleChildCount()
217 return AccessibleStaticTextBase::getAccessibleChildCount();
220 uno::Reference
< XAccessible
> SAL_CALL
221 ScAccessibleCell::getAccessibleChild(sal_Int64 nIndex
)
223 return AccessibleStaticTextBase::getAccessibleChild(nIndex
);
227 ScAccessibleCell::getAccessibleStateSet()
229 SolarMutexGuard aGuard
;
230 sal_Int64 nParentStates
= 0;
231 if (getAccessibleParent().is())
233 uno::Reference
<XAccessibleContext
> xParentContext
= getAccessibleParent()->getAccessibleContext();
234 nParentStates
= xParentContext
->getAccessibleStateSet();
236 sal_Int64 nStateSet
= 0;
237 if (IsDefunc(nParentStates
))
238 nStateSet
|= AccessibleStateType::DEFUNC
;
242 nStateSet
|= AccessibleStateType::FOCUSED
;
246 nStateSet
|= AccessibleStateType::ENABLED
;
247 nStateSet
|= AccessibleStateType::MULTI_LINE
;
248 nStateSet
|= AccessibleStateType::MULTI_SELECTABLE
;
250 nStateSet
|= AccessibleStateType::OPAQUE
;
251 nStateSet
|= AccessibleStateType::SELECTABLE
;
253 nStateSet
|= AccessibleStateType::SELECTED
;
255 nStateSet
|= AccessibleStateType::SHOWING
;
256 nStateSet
|= AccessibleStateType::TRANSIENT
;
258 nStateSet
|= AccessibleStateType::VISIBLE
;
261 if (IsEditable(nParentStates
))
263 nStateSet
|= AccessibleStateType::EDITABLE
;
264 nStateSet
|= AccessibleStateType::RESIZABLE
;
266 nStateSet
|= AccessibleStateType::ENABLED
;
267 nStateSet
|= AccessibleStateType::MULTI_LINE
;
268 nStateSet
|= AccessibleStateType::MULTI_SELECTABLE
;
269 nStateSet
|= AccessibleStateType::FOCUSABLE
;
271 nStateSet
|= AccessibleStateType::OPAQUE
;
272 nStateSet
|= AccessibleStateType::SELECTABLE
;
274 nStateSet
|= AccessibleStateType::SELECTED
;
276 nStateSet
|= AccessibleStateType::SHOWING
;
277 nStateSet
|= AccessibleStateType::TRANSIENT
;
279 nStateSet
|= AccessibleStateType::VISIBLE
;
284 uno::Reference
<XAccessibleRelationSet
> SAL_CALL
285 ScAccessibleCell::getAccessibleRelationSet()
287 SolarMutexGuard aGuard
;
289 rtl::Reference
<utl::AccessibleRelationSetHelper
> pRelationSet
;
291 pRelationSet
= mpAccDoc
->GetRelationSet(&maCellAddress
);
293 pRelationSet
= new utl::AccessibleRelationSetHelper();
294 FillDependents(pRelationSet
.get());
295 FillPrecedents(pRelationSet
.get());
299 //===== XServiceInfo ====================================================
301 OUString SAL_CALL
ScAccessibleCell::getImplementationName()
303 return u
"ScAccessibleCell"_ustr
;
306 uno::Sequence
< OUString
> SAL_CALL
307 ScAccessibleCell::getSupportedServiceNames()
309 const css::uno::Sequence
<OUString
> vals
{ u
"com.sun.star.sheet.AccessibleCell"_ustr
};
310 return comphelper::concatSequences(ScAccessibleContextBase::getSupportedServiceNames(), vals
);
313 //==== internal =========================================================
315 bool ScAccessibleCell::IsDefunc(sal_Int64 nParentStates
)
317 return ScAccessibleContextBase::IsDefunc() || (mpDoc
== nullptr) || (mpViewShell
== nullptr) || !getAccessibleParent().is() ||
318 (nParentStates
& AccessibleStateType::DEFUNC
);
321 bool ScAccessibleCell::IsEditable(sal_Int64 nParentStates
)
323 bool bEditable(true);
324 if ( !(nParentStates
& AccessibleStateType::EDITABLE
) &&
327 // here I have to test whether the protection of the table should influence this cell.
328 const ScProtectionAttr
* pItem
= mpDoc
->GetAttr(maCellAddress
, ATTR_PROTECTION
);
330 bEditable
= !pItem
->GetProtection();
335 bool ScAccessibleCell::IsOpaque() const
337 // test whether there is a background color
341 const SvxBrushItem
* pItem
= mpDoc
->GetAttr(maCellAddress
, ATTR_BACKGROUND
);
343 bOpaque
= pItem
->GetColor() != COL_TRANSPARENT
;
348 bool ScAccessibleCell::IsFocused() const
350 if (mpViewShell
&& mpViewShell
->GetViewData().GetCurPos() == maCellAddress
)
351 return mpViewShell
->GetActiveWin()->HasFocus();
356 bool ScAccessibleCell::IsSelected()
360 const ScAccessibleSpreadsheet
*pSheet
=static_cast<const ScAccessibleSpreadsheet
*>(mxParent
.get());
363 return pSheet
->IsScAddrFormulaSel(maCellAddress
);
371 const ScMarkData
& rMarkdata
= mpViewShell
->GetViewData().GetMarkData();
372 bResult
= rMarkdata
.IsCellMarked(maCellAddress
.Col(), maCellAddress
.Row());
377 ScDocument
* ScAccessibleCell::GetDocument(ScTabViewShell
* pViewShell
)
379 ScDocument
* pDoc
= nullptr;
381 pDoc
= &pViewShell
->GetViewData().GetDocument();
385 ::std::unique_ptr
< SvxEditSource
> ScAccessibleCell::CreateEditSource(ScTabViewShell
* pViewShell
, ScAddress aCell
, ScSplitPos eSplitPos
)
389 return ::std::unique_ptr
< SvxEditSource
>();
391 ::std::unique_ptr
< SvxEditSource
> pEditSource (new ScAccessibilityEditSource(std::make_unique
<ScAccessibleCellTextData
>(pViewShell
, aCell
, eSplitPos
, this)));
396 void ScAccessibleCell::FillDependents(utl::AccessibleRelationSetHelper
* pRelationSet
)
401 ScRange
aRange(0, 0, maCellAddress
.Tab(), mpDoc
->MaxCol(), mpDoc
->MaxRow(), maCellAddress
.Tab());
402 ScCellIterator
aCellIter(*mpDoc
, aRange
);
404 for (bool bHasCell
= aCellIter
.first(); bHasCell
; bHasCell
= aCellIter
.next())
406 if (aCellIter
.getType() == CELLTYPE_FORMULA
)
409 ScDetectiveRefIter
aIter(*mpDoc
, aCellIter
.getFormulaCell());
411 while ( !bFound
&& aIter
.GetNextRef( aRef
) )
413 if (aRef
.Contains(maCellAddress
))
417 AddRelation(aCellIter
.GetPos(), AccessibleRelationType_CONTROLLER_FOR
, pRelationSet
);
422 void ScAccessibleCell::FillPrecedents(utl::AccessibleRelationSetHelper
* pRelationSet
)
427 ScRefCellValue
aCell(*mpDoc
, maCellAddress
);
428 if (aCell
.getType() == CELLTYPE_FORMULA
)
430 ScFormulaCell
* pCell
= aCell
.getFormula();
431 ScDetectiveRefIter
aIter(*mpDoc
, pCell
);
433 while ( aIter
.GetNextRef( aRef
) )
435 AddRelation( aRef
, AccessibleRelationType_CONTROLLED_BY
, pRelationSet
);
440 void ScAccessibleCell::AddRelation(const ScAddress
& rCell
,
441 const AccessibleRelationType eRelationType
,
442 utl::AccessibleRelationSetHelper
* pRelationSet
)
444 AddRelation(ScRange(rCell
, rCell
), eRelationType
, pRelationSet
);
447 void ScAccessibleCell::AddRelation(const ScRange
& rRange
,
448 const AccessibleRelationType eRelationType
,
449 utl::AccessibleRelationSetHelper
* pRelationSet
)
451 uno::Reference
< XAccessibleTable
> xTable ( getAccessibleParent()->getAccessibleContext(), uno::UNO_QUERY
);
455 const sal_uInt32
nCount(static_cast<sal_uInt32
>(rRange
.aEnd
.Col() -
456 rRange
.aStart
.Col() + 1) * (rRange
.aEnd
.Row() -
457 rRange
.aStart
.Row() + 1));
459 // tdf#157299 avoid handling a large amount of cells for performance reasons
462 SAL_WARN("sc", "ScAccessibleCell::AddRelation: Not setting relations "
463 "for cell range with more than 1000 cells for performance reasons.");
467 uno::Sequence
<uno::Reference
<css::accessibility::XAccessible
>> aTargetSet(nCount
);
468 uno::Reference
<css::accessibility::XAccessible
>* pTargetSet
= aTargetSet
.getArray();
470 for (sal_uInt32 nRow
= rRange
.aStart
.Row(); nRow
<= sal::static_int_cast
<sal_uInt32
>(rRange
.aEnd
.Row()); ++nRow
)
472 for (sal_uInt32 nCol
= rRange
.aStart
.Col(); nCol
<= sal::static_int_cast
<sal_uInt32
>(rRange
.aEnd
.Col()); ++nCol
)
474 pTargetSet
[nPos
] = xTable
->getAccessibleCellAt(nRow
, nCol
);
478 OSL_ENSURE(nCount
== nPos
, "something went wrong");
479 AccessibleRelation aRelation
;
480 aRelation
.RelationType
= eRelationType
;
481 aRelation
.TargetSet
= std::move(aTargetSet
);
482 pRelationSet
->AddRelation(aRelation
);
485 static OUString
ReplaceFourChar(const OUString
& oldOUString
)
487 return oldOUString
.replaceAll(u
"\\", u
"\\\\")
488 .replaceAll(u
";", u
"\\;")
489 .replaceAll(u
"=", u
"\\=")
490 .replaceAll(u
",", u
"\\,")
491 .replaceAll(u
":", u
"\\:");
494 uno::Any SAL_CALL
ScAccessibleCell::getExtendedAttributes()
496 SolarMutexGuard aGuard
;
498 // report row and column index text via attributes as specified in ARIA which map
499 // to attributes of the same name for AT-SPI2, IAccessible2, UIA
500 // https://www.w3.org/TR/core-aam-1.2/#ariaRowIndexText
501 // https://www.w3.org/TR/core-aam-1.2/#ariaColIndexText
502 const OUString sRowIndexText
= maCellAddress
.Format(ScRefFlags::ROW_VALID
);
503 const OUString sColIndexText
= maCellAddress
.Format(ScRefFlags::COL_VALID
);
504 OUString sAttributes
= "rowindextext:" + sRowIndexText
+ ";colindextext:" + sColIndexText
+ ";";
508 OUString strFor
= mpViewShell
->GetFormula(maCellAddress
) ;
509 if (!strFor
.isEmpty())
511 strFor
= strFor
.copy(1);
512 strFor
= ReplaceFourChar(strFor
);
514 strFor
= "Formula:" + strFor
+
516 ReplaceFourChar(GetAllDisplayNote()) + ";" +
517 getShadowAttrs() + //the string returned contains the spliter ";"
518 getBorderAttrs();//the string returned contains the spliter ";"
519 //end of cell attributes
522 strFor
+= "isdropdown:";
529 sAttributes
+= strFor
;
532 return uno::Any(sAttributes
);
535 // 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.
536 uno::Sequence
< beans::PropertyValue
> SAL_CALL
ScAccessibleCell::getCharacterAttributes( sal_Int32 nIndex
, const css::uno::Sequence
< OUString
>& aRequestedAttributes
)
538 SolarMutexGuard aGuard
;
540 uno::Sequence
< beans::PropertyValue
> aAttribs
= AccessibleStaticTextBase::getCharacterAttributes( nIndex
, aRequestedAttributes
);
542 sal_uInt16 nParaIndent
= mpDoc
->GetAttr( maCellAddress
, ATTR_INDENT
)->GetValue();
545 auto [begin
, end
] = asNonConstRange(aAttribs
);
546 auto pAttrib
= std::find_if(begin
, end
,
547 [](const beans::PropertyValue
& rAttrib
) { return "ParaLeftMargin" == rAttrib
.Name
; });
549 pAttrib
->Value
<<= nParaIndent
;
554 bool ScAccessibleCell::IsFormulaMode()
556 ScAccessibleSpreadsheet
* pSheet
= static_cast<ScAccessibleSpreadsheet
*>(mxParent
.get());
559 return pSheet
->IsFormulaMode();
564 bool ScAccessibleCell::IsDropdown() const
566 sal_uInt16 nPosX
= maCellAddress
.Col();
567 sal_uInt16 nPosY
= sal_uInt16(maCellAddress
.Row());
568 sal_uInt16 nTab
= maCellAddress
.Tab();
569 sal_uInt32 nValidation
= mpDoc
->GetAttr( nPosX
, nPosY
, nTab
, ATTR_VALIDDATA
)->GetValue();
572 const ScValidationData
* pData
= mpDoc
->GetValidationEntry( nValidation
);
573 if( pData
&& pData
->HasSelectionList() )
576 const ScMergeFlagAttr
* pAttr
= mpDoc
->GetAttr( nPosX
, nPosY
, nTab
, ATTR_MERGE_FLAG
);
577 if( pAttr
->HasAutoFilter() )
583 sal_uInt16 nTabCount
= mpDoc
->GetTableCount();
584 if ( nTab
+1<nTabCount
&& mpDoc
->IsScenario(nTab
+1) && !mpDoc
->IsScenario(nTab
) )
587 ScMarkData
aMarks(mpDoc
->GetSheetLimits());
588 for (i
=nTab
+1; i
<nTabCount
&& mpDoc
->IsScenario(i
); i
++)
589 mpDoc
->MarkScenario( i
, nTab
, aMarks
, false, ScScenarioFlags::ShowFrame
);
591 aMarks
.FillRangeListWithMarks( &aRanges
, false );
593 SCTAB nRangeCount
= aRanges
.size();
594 for (i
=0; i
<nRangeCount
; i
++)
596 ScRange aRange
= aRanges
[i
];
597 mpDoc
->ExtendTotalMerge( aRange
);
598 bool bTextBelow
= ( aRange
.aStart
.Row() == 0 );
599 // MT IA2: Not used: sal_Bool bIsInScen = sal_False;
602 bHasScenario
= (aRange
.aStart
.Col() == nPosX
&& aRange
.aEnd
.Row() == nPosY
-1);
606 bHasScenario
= (aRange
.aStart
.Col() == nPosX
&& aRange
.aStart
.Row() == nPosY
+1);
608 if( bHasScenario
) return true;
615 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */