Add a comment to clarify what kind of inputs the class handles
[LibreOffice.git] / sw / source / core / access / accpara.cxx
blob36c7fff36ac48f43043c4449979361b789bf1c3d
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 .
20 #include <memory>
21 #include <numeric>
22 #include <txtfrm.hxx>
23 #include <flyfrm.hxx>
24 #include <mdiexp.hxx>
25 #include <ndtxt.hxx>
26 #include <pam.hxx>
27 #include <unotextrange.hxx>
28 #include <unocrsrhelper.hxx>
29 #include <crstate.hxx>
30 #include <accmap.hxx>
31 #include <fesh.hxx>
32 #include <viewopt.hxx>
33 #include <vcl/svapp.hxx>
34 #include <vcl/window.hxx>
35 #include <sal/log.hxx>
36 #include <com/sun/star/accessibility/AccessibleRole.hpp>
37 #include <com/sun/star/accessibility/AccessibleScrollType.hpp>
38 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
39 #include <com/sun/star/accessibility/AccessibleTextType.hpp>
40 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
41 #include <com/sun/star/i18n/Boundary.hpp>
42 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
43 #include <com/sun/star/i18n/WordType.hpp>
44 #include <com/sun/star/i18n/XBreakIterator.hpp>
45 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
46 #include <com/sun/star/beans/UnknownPropertyException.hpp>
47 #include <breakit.hxx>
48 #include "accpara.hxx"
49 #include "accportions.hxx"
50 #include <sfx2/viewsh.hxx>
51 #include <sfx2/viewfrm.hxx>
52 #include <sfx2/dispatch.hxx>
53 #include <unocrsr.hxx>
54 #include <unoport.hxx>
55 #include <doc.hxx>
56 #include <IDocumentRedlineAccess.hxx>
57 #include "acchyperlink.hxx"
58 #include "acchypertextdata.hxx"
59 #include <unotools/accessiblerelationsethelper.hxx>
60 #include <com/sun/star/accessibility/AccessibleRelationType.hpp>
61 #include <comphelper/accessibletexthelper.hxx>
62 #include <algorithm>
63 #include <docufld.hxx>
64 #include <txtfld.hxx>
65 #include <fmtfld.hxx>
66 #include <modcfg.hxx>
67 #include <com/sun/star/beans/XPropertySet.hpp>
68 #include <swmodule.hxx>
69 #include <redline.hxx>
70 #include <com/sun/star/awt/FontWeight.hpp>
71 #include <com/sun/star/awt/FontStrikeout.hpp>
72 #include <com/sun/star/awt/FontSlant.hpp>
73 #include <wrong.hxx>
74 #include <editeng/brushitem.hxx>
75 #include <editeng/unoprnms.hxx>
76 #include <editeng/lrspitem.hxx>
77 #include <editeng/ulspitem.hxx>
78 #include <swatrset.hxx>
79 #include <unosett.hxx>
80 #include <unomap.hxx>
81 #include <unoprnms.hxx>
82 #include <com/sun/star/text/WritingMode2.hpp>
83 #include <viewimp.hxx>
84 #include "textmarkuphelper.hxx"
85 #include "parachangetrackinginfo.hxx"
86 #include <com/sun/star/text/TextMarkupType.hpp>
87 #include <cppuhelper/supportsservice.hxx>
88 #include <cppuhelper/typeprovider.hxx>
89 #include <svx/colorwindow.hxx>
90 #include <o3tl/string_view.hxx>
91 #include <editeng/editids.hrc>
93 #include <reffld.hxx>
94 #include <flddat.hxx>
95 #include "../../uibase/inc/fldmgr.hxx"
96 #include <fldbas.hxx> // SwField
98 using namespace ::com::sun::star;
99 using namespace ::com::sun::star::accessibility;
101 using beans::PropertyValue;
102 using beans::UnknownPropertyException;
103 using beans::PropertyState_DIRECT_VALUE;
105 using std::max;
106 using std::min;
107 using std::sort;
109 namespace com::sun::star::text {
110 class XText;
113 constexpr OUString sServiceName = u"com.sun.star.text.AccessibleParagraphView"_ustr;
114 constexpr OUStringLiteral sImplementationName = u"com.sun.star.comp.Writer.SwAccessibleParagraphView";
116 const SwTextFrame* SwAccessibleParagraph::GetTextFrame() const
118 return static_cast<const SwTextFrame*>(GetFrame());
121 OUString const & SwAccessibleParagraph::GetString()
123 return GetPortionData().GetAccessibleString();
126 const OUString & SwAccessibleParagraph::GetDescription()
128 return EMPTY_OUSTRING; // provide empty description for paragraphs
131 sal_Int32 SwAccessibleParagraph::GetCaretPos()
133 sal_Int32 nRet = -1;
135 // get the selection's point, and test whether it's in our node
136 // #i27301# - consider adjusted method signature
137 SwPaM* pCaret = GetCursor( false ); // caret is first PaM in PaM-ring
139 if( pCaret != nullptr )
141 const SwTextFrame* const pTextFrame = GetTextFrame();
142 assert(pTextFrame);
144 // check whether the point points into 'our' node
145 SwPosition* pPoint = pCaret->GetPoint();
146 if (sw::FrameContainsNode(*pTextFrame, pPoint->GetNodeIndex()))
148 // same node? Then check whether it's also within 'our' part
149 // of the paragraph
150 const TextFrameIndex nIndex = pTextFrame->MapModelToViewPos(*pPoint);
151 if(!GetPortionData().IsValidCorePosition( nIndex ) ||
152 (GetPortionData().IsZeroCorePositionData()
153 && nIndex == TextFrameIndex(0)))
155 bool bFormat = pTextFrame->HasPara();
156 if(bFormat)
158 ClearPortionData();
159 UpdatePortionData();
162 if( GetPortionData().IsValidCorePosition( nIndex ) )
164 // Yes, it's us!
165 // consider that cursor/caret is in front of the list label
166 if ( pCaret->IsInFrontOfLabel() )
168 nRet = 0;
170 else
172 nRet = GetPortionData().GetAccessiblePosition( nIndex );
175 OSL_ENSURE( nRet >= 0, "invalid cursor?" );
176 OSL_ENSURE( nRet <= GetPortionData().GetAccessibleString().
177 getLength(), "invalid cursor?" );
179 // else: in this paragraph, but in different frame
181 // else: not in this paragraph
183 // else: no cursor -> no caret
185 return nRet;
188 // #i27301# - new parameter <_bForSelection>
189 SwPaM* SwAccessibleParagraph::GetCursor( const bool _bForSelection )
191 // get the cursor shell; if we don't have any, we don't have a
192 // cursor/selection either
193 SwPaM* pCursor = nullptr;
194 SwCursorShell* pCursorShell = SwAccessibleParagraph::GetCursorShell();
195 // #i27301# - if cursor is retrieved for selection, the cursors for
196 // a table selection has to be returned.
197 if ( pCursorShell != nullptr &&
198 ( _bForSelection || !pCursorShell->IsTableMode() ) )
200 SwFEShell *pFESh = dynamic_cast<SwFEShell*>(pCursorShell);
201 if( !pFESh ||
202 !(pFESh->IsFrameSelected() || pFESh->GetSelectedObjCount() > 0) )
204 // get the selection, and test whether it affects our text node
205 pCursor = pCursorShell->GetCursor( false /* ??? */ );
209 return pCursor;
212 bool SwAccessibleParagraph::IsHeading() const
214 const SwTextFrame* const pFrame = GetTextFrame();
215 const SwTextNode *pTextNd = pFrame->GetTextNodeForParaProps();
216 return pTextNd->IsOutline();
219 void SwAccessibleParagraph::GetStates( sal_Int64& rStateSet )
221 SwAccessibleContext::GetStates( rStateSet );
223 // MULTILINE
224 rStateSet |= AccessibleStateType::MULTI_LINE;
226 if (GetCursorShell())
228 // MULTISELECTABLE
229 rStateSet |= AccessibleStateType::MULTI_SELECTABLE;
230 // FOCUSABLE
231 rStateSet |= AccessibleStateType::FOCUSABLE;
234 // FOCUSED (simulates node index of cursor)
235 SwPaM* pCaret = GetCursor( false ); // #i27301# - consider adjusted method signature
236 const SwTextFrame* const pFrame = GetTextFrame();
237 assert(pFrame);
238 if (pCaret != nullptr &&
239 sw::FrameContainsNode(*pFrame, pCaret->GetPoint()->GetNodeIndex()) &&
240 HasCursor())
242 vcl::Window *pWin = GetWindow();
243 if( pWin && pWin->HasFocus() )
244 rStateSet |= AccessibleStateType::FOCUSED;
245 ::rtl::Reference < SwAccessibleContext > xThis( this );
246 GetMap()->SetCursorContext( xThis );
250 void SwAccessibleParagraph::InvalidateContent_( bool bVisibleDataFired )
252 OUString sOldText( GetString() );
254 ClearPortionData();
256 const OUString sText = GetString();
258 if( sText != sOldText )
260 // The text is changed
261 AccessibleEventObject aEvent;
262 aEvent.EventId = AccessibleEventId::TEXT_CHANGED;
264 // determine exact changes between sOldText and sText
265 (void)comphelper::OCommonAccessibleText::implInitTextChangedEvent(sOldText, sText,
266 aEvent.OldValue,
267 aEvent.NewValue);
269 FireAccessibleEvent( aEvent );
270 uno::Reference< XAccessible > xparent = getAccessibleParent();
271 uno::Reference< XAccessibleContext > xAccContext(xparent,uno::UNO_QUERY);
272 if (xAccContext.is() && xAccContext->getAccessibleRole() == AccessibleRole::TABLE_CELL)
274 SwAccessibleContext* pPara = static_cast< SwAccessibleContext* >(xparent.get());
275 if(pPara)
277 AccessibleEventObject aParaEvent;
278 aParaEvent.EventId = AccessibleEventId::VALUE_CHANGED;
279 pPara->FireAccessibleEvent(aParaEvent);
283 else if( !bVisibleDataFired )
285 FireVisibleDataEvent();
288 bool bNewIsBlockQuote = IsBlockQuote();
289 bool bNewIsHeading = IsHeading();
290 //Get the real heading level, Heading1 ~ Heading10
291 m_nHeadingLevel = GetRealHeadingLevel();
292 bool bOldIsBlockQuote;
293 bool bOldIsHeading;
295 std::scoped_lock aGuard( m_Mutex );
296 bOldIsBlockQuote = m_bIsBlockQuote;
297 bOldIsHeading = m_bIsHeading;
298 m_bIsBlockQuote = bNewIsBlockQuote;
299 if( m_bIsHeading != bNewIsHeading )
300 m_bIsHeading = bNewIsHeading;
303 if (bNewIsBlockQuote != bOldIsBlockQuote || bNewIsHeading != bOldIsHeading)
305 // The role has changed
306 AccessibleEventObject aEvent;
307 aEvent.EventId = AccessibleEventId::ROLE_CHANGED;
309 FireAccessibleEvent( aEvent );
312 if( sText == sOldText )
313 return;
315 OUString sNewDesc( GetDescription() );
316 OUString sOldDesc;
318 std::scoped_lock aGuard( m_Mutex );
319 sOldDesc = m_sDesc;
320 if( m_sDesc != sNewDesc )
321 m_sDesc = sNewDesc;
324 if( sNewDesc != sOldDesc )
326 // The text is changed
327 AccessibleEventObject aEvent;
328 aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED;
329 aEvent.OldValue <<= sOldDesc;
330 aEvent.NewValue <<= sNewDesc;
332 FireAccessibleEvent( aEvent );
336 void SwAccessibleParagraph::InvalidateCursorPos_()
338 // The text is changed
339 sal_Int32 nNew = GetCaretPos();
340 sal_Int32 nOld;
342 std::scoped_lock aGuard( m_Mutex );
343 nOld = m_nOldCaretPos;
344 m_nOldCaretPos = nNew;
346 if( -1 != nNew )
348 // remember that object as the one that has the caret. This is
349 // necessary to notify that object if the cursor leaves it.
350 ::rtl::Reference < SwAccessibleContext > xThis( this );
351 GetMap()->SetCursorContext( xThis );
354 vcl::Window *pWin = GetWindow();
355 if( nOld == nNew )
356 return;
358 // The cursor's node position is simulated by the focus!
359 if( pWin && pWin->HasFocus() && -1 == nOld )
360 FireStateChangedEvent( AccessibleStateType::FOCUSED, true );
362 AccessibleEventObject aEvent;
363 aEvent.EventId = AccessibleEventId::CARET_CHANGED;
364 aEvent.OldValue <<= nOld;
365 aEvent.NewValue <<= nNew;
367 FireAccessibleEvent( aEvent );
369 if( pWin && pWin->HasFocus() && -1 == nNew )
370 FireStateChangedEvent( AccessibleStateType::FOCUSED, false );
371 //To send TEXT_SELECTION_CHANGED event
372 sal_Int32 nStart=0;
373 sal_Int32 nEnd =0;
374 bool bCurSelection = GetSelection(nStart,nEnd);
375 if(m_bLastHasSelection || bCurSelection )
377 aEvent.EventId = AccessibleEventId::TEXT_SELECTION_CHANGED;
378 aEvent.OldValue.clear();
379 aEvent.NewValue.clear();
380 FireAccessibleEvent(aEvent);
382 m_bLastHasSelection =bCurSelection;
386 void SwAccessibleParagraph::InvalidateFocus_()
388 vcl::Window *pWin = GetWindow();
389 if( pWin )
391 sal_Int32 nPos;
393 std::scoped_lock aGuard( m_Mutex );
394 nPos = m_nOldCaretPos;
396 OSL_ENSURE( nPos != -1, "focus object should be selected" );
398 FireStateChangedEvent( AccessibleStateType::FOCUSED,
399 pWin->HasFocus() && nPos != -1 );
403 SwAccessibleParagraph::SwAccessibleParagraph(
404 std::shared_ptr<SwAccessibleMap> const& pInitMap,
405 const SwTextFrame& rTextFrame )
406 : SwAccessibleParagraph_BASE(pInitMap, AccessibleRole::PARAGRAPH, &rTextFrame)
407 , m_nOldCaretPos( -1 )
408 , m_bIsBlockQuote(false)
409 , m_bIsHeading( false )
410 //Get the real heading level, Heading1 ~ Heading10
411 , m_nHeadingLevel (-1)
412 , m_aSelectionHelper( *this )
413 , mpParaChangeTrackInfo( new SwParaChangeTrackingInfo( rTextFrame ) ) // #i108125#
414 , m_bLastHasSelection(false) //To add TEXT_SELECTION_CHANGED event
416 StartListening(const_cast<SwTextFrame&>(rTextFrame));
417 m_bIsBlockQuote = IsBlockQuote();
418 m_bIsHeading = IsHeading();
419 //Get the real heading level, Heading1 ~ Heading10
420 m_nHeadingLevel = GetRealHeadingLevel();
421 SetName( OUString() ); // set an empty accessibility name for paragraphs
424 SwAccessibleParagraph::~SwAccessibleParagraph()
426 SolarMutexGuard aGuard;
428 m_pPortionData.reset();
429 m_pHyperTextData.reset();
430 mpParaChangeTrackInfo.reset(); // #i108125#
431 EndListeningAll();
434 bool SwAccessibleParagraph::HasCursor()
436 std::scoped_lock aGuard( m_Mutex );
437 return m_nOldCaretPos != -1;
440 void SwAccessibleParagraph::UpdatePortionData()
442 // obtain the text frame
443 const SwTextFrame* pFrame = GetTextFrame();
444 OSL_ENSURE( pFrame != nullptr, "The text frame has vanished!" );
445 if (!pFrame)
446 ClearPortionData();
447 else
449 OSL_ENSURE( pFrame->IsTextFrame(), "The text frame has mutated!" );
450 // build new portion data
451 m_pPortionData.reset( new SwAccessiblePortionData(
452 pFrame, GetMap()->GetShell()->GetViewOptions()) );
453 pFrame->VisitPortions( *m_pPortionData );
455 OSL_ENSURE( m_pPortionData != nullptr, "UpdatePortionData() failed" );
458 void SwAccessibleParagraph::ClearPortionData()
460 m_pPortionData.reset();
461 m_pHyperTextData.reset();
464 void SwAccessibleParagraph::ExecuteAtViewShell( sal_uInt16 nSlot )
466 OSL_ENSURE( GetMap() != nullptr, "no map?" );
467 SwViewShell* pViewShell = GetMap()->GetShell();
469 assert(pViewShell != nullptr && "View shell expected!");
470 SfxViewShell* pSfxShell = pViewShell->GetSfxViewShell();
472 OSL_ENSURE( pSfxShell != nullptr, "SfxViewShell shell expected!" );
473 if( !pSfxShell )
474 return;
476 SfxViewFrame& rFrame = pSfxShell->GetViewFrame();
477 SfxDispatcher *pDispatcher = rFrame.GetDispatcher();
478 OSL_ENSURE( pDispatcher != nullptr, "Dispatcher expected!" );
479 if( !pDispatcher )
480 return;
482 pDispatcher->Execute( nSlot );
485 rtl::Reference<SwXTextPortion> SwAccessibleParagraph::CreateUnoPortion(
486 sal_Int32 nStartIndex,
487 sal_Int32 nEndIndex )
489 OSL_ENSURE( (IsValidChar(nStartIndex, GetString().getLength()) &&
490 (nEndIndex == -1)) ||
491 IsValidRange(nStartIndex, nEndIndex, GetString().getLength()),
492 "please check parameters before calling this method" );
494 const TextFrameIndex nStart = GetPortionData().GetCoreViewPosition(nStartIndex);
495 const TextFrameIndex nEnd = (nEndIndex == -1)
496 ? (nStart + TextFrameIndex(1))
497 : GetPortionData().GetCoreViewPosition(nEndIndex);
499 // create UNO cursor
500 const SwTextFrame* const pFrame = GetTextFrame();
501 SwPosition aStartPos(pFrame->MapViewToModelPos(nStart));
502 auto pUnoCursor(const_cast<SwDoc&>(pFrame->GetDoc()).CreateUnoCursor(aStartPos));
503 pUnoCursor->SetMark();
504 *pUnoCursor->GetMark() = pFrame->MapViewToModelPos(nEnd);
506 // create a (dummy) text portion to be returned
507 uno::Reference<SwXText> aEmpty;
508 return new SwXTextPortion ( pUnoCursor.get(), aEmpty, PORTION_TEXT);
511 // range checking for parameter
513 bool SwAccessibleParagraph::IsValidChar(
514 sal_Int32 nPos, sal_Int32 nLength)
516 return (nPos >= 0) && (nPos < nLength);
519 bool SwAccessibleParagraph::IsValidPosition(
520 sal_Int32 nPos, sal_Int32 nLength)
522 return (nPos >= 0) && (nPos <= nLength);
525 bool SwAccessibleParagraph::IsValidRange(
526 sal_Int32 nBegin, sal_Int32 nEnd, sal_Int32 nLength)
528 return IsValidPosition(nBegin, nLength) && IsValidPosition(nEnd, nLength);
531 //the function is to check whether the position is in a redline range.
532 const SwRangeRedline* SwAccessibleParagraph::GetRedlineAtIndex()
534 const SwRangeRedline* pRedline = nullptr;
535 SwPaM* pCrSr = GetCursor( true );
536 if ( pCrSr )
538 SwPosition* pStart = pCrSr->Start();
539 pRedline = pStart->GetDoc().getIDocumentRedlineAccess().GetRedline(*pStart, nullptr);
542 return pRedline;
545 // text boundaries
547 bool SwAccessibleParagraph::GetCharBoundary(
548 i18n::Boundary& rBound,
549 std::u16string_view text,
550 sal_Int32 nPos )
552 if( GetPortionData().FillBoundaryIFDateField( rBound, nPos) )
553 return true;
555 auto nPosEnd = nPos;
556 o3tl::iterateCodePoints(text, &nPosEnd);
558 rBound.startPos = nPos;
559 rBound.endPos = nPosEnd;
561 return true;
564 bool SwAccessibleParagraph::GetWordBoundary(
565 i18n::Boundary& rBound,
566 const OUString& rText,
567 sal_Int32 nPos )
569 // now ask the Break-Iterator for the word
570 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
572 // get locale for this position
573 const SwTextFrame* const pFrame = GetTextFrame();
574 const TextFrameIndex nCorePos = GetPortionData().GetCoreViewPosition(nPos);
575 lang::Locale aLocale = g_pBreakIt->GetLocale(pFrame->GetLangOfChar(nCorePos, 0, true));
577 // which type of word are we interested in?
578 // (DICTIONARY_WORD includes punctuation, ANY_WORD doesn't.)
579 const sal_Int16 nWordType = i18n::WordType::ANY_WORD;
581 // get word boundary, as the Break-Iterator sees fit.
582 rBound = g_pBreakIt->GetBreakIter()->getWordBoundary(
583 rText, nPos, aLocale, nWordType, true );
585 return true;
588 bool SwAccessibleParagraph::GetSentenceBoundary(
589 i18n::Boundary& rBound,
590 const OUString& rText,
591 sal_Int32 nPos )
593 const sal_Unicode* pStr = rText.getStr();
594 while( nPos < rText.getLength() && pStr[nPos] == u' ' )
595 nPos++;
597 GetPortionData().GetSentenceBoundary( rBound, nPos );
598 return true;
601 bool SwAccessibleParagraph::GetLineBoundary(
602 i18n::Boundary& rBound,
603 std::u16string_view aText,
604 sal_Int32 nPos )
606 if( sal_Int32(aText.size()) == nPos )
607 GetPortionData().GetLastLineBoundary( rBound );
608 else
609 GetPortionData().GetLineBoundary( rBound, nPos );
610 return true;
613 bool SwAccessibleParagraph::GetParagraphBoundary(
614 i18n::Boundary& rBound,
615 std::u16string_view aText )
617 rBound.startPos = 0;
618 rBound.endPos = aText.size();
619 return true;
622 bool SwAccessibleParagraph::GetAttributeBoundary(
623 i18n::Boundary& rBound,
624 sal_Int32 nPos )
626 GetPortionData().GetAttributeBoundary( rBound, nPos );
627 return true;
630 bool SwAccessibleParagraph::GetGlyphBoundary(
631 i18n::Boundary& rBound,
632 const OUString& rText,
633 sal_Int32 nPos )
635 // ask the Break-Iterator for the glyph by moving one cell
636 // forward, and then one cell back
637 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
639 // get locale for this position
640 const SwTextFrame* const pFrame = GetTextFrame();
641 const TextFrameIndex nCorePos = GetPortionData().GetCoreViewPosition(nPos);
642 lang::Locale aLocale = g_pBreakIt->GetLocale(pFrame->GetLangOfChar(nCorePos, 0, true));
644 // get word boundary, as the Break-Iterator sees fit.
645 const sal_Int16 nIterMode = i18n::CharacterIteratorMode::SKIPCELL;
646 sal_Int32 nDone = 0;
647 rBound.endPos = g_pBreakIt->GetBreakIter()->nextCharacters(
648 rText, nPos, aLocale, nIterMode, 1, nDone );
649 rBound.startPos = g_pBreakIt->GetBreakIter()->previousCharacters(
650 rText, rBound.endPos, aLocale, nIterMode, 1, nDone );
651 bool bRet = ((rBound.startPos <= nPos) && (nPos <= rBound.endPos));
652 OSL_ENSURE( rBound.startPos <= nPos, "start pos too high" );
653 OSL_ENSURE( rBound.endPos >= nPos, "end pos too low" );
655 return bRet;
658 bool SwAccessibleParagraph::GetTextBoundary(
659 i18n::Boundary& rBound,
660 const OUString& rText,
661 sal_Int32 nPos,
662 sal_Int16 nTextType )
664 // error checking
665 if( !( AccessibleTextType::LINE == nTextType
666 ? IsValidPosition( nPos, rText.getLength() )
667 : IsValidChar( nPos, rText.getLength() ) ) )
668 throw lang::IndexOutOfBoundsException();
670 bool bRet;
672 switch( nTextType )
674 case AccessibleTextType::WORD:
675 bRet = GetWordBoundary(rBound, rText, nPos);
676 break;
678 case AccessibleTextType::SENTENCE:
679 bRet = GetSentenceBoundary( rBound, rText, nPos );
680 break;
682 case AccessibleTextType::PARAGRAPH:
683 bRet = GetParagraphBoundary( rBound, rText );
684 break;
686 case AccessibleTextType::CHARACTER:
687 bRet = GetCharBoundary( rBound, rText, nPos );
688 break;
690 case AccessibleTextType::LINE:
691 //Solve the problem of returning wrong LINE and PARAGRAPH
692 if((nPos == rText.getLength()) && nPos > 0)
693 bRet = GetLineBoundary( rBound, rText, nPos - 1);
694 else
695 bRet = GetLineBoundary( rBound, rText, nPos );
696 break;
698 case AccessibleTextType::ATTRIBUTE_RUN:
699 bRet = GetAttributeBoundary( rBound, nPos );
700 break;
702 case AccessibleTextType::GLYPH:
703 bRet = GetGlyphBoundary( rBound, rText, nPos );
704 break;
706 default:
707 throw lang::IllegalArgumentException( );
710 return bRet;
713 OUString SAL_CALL SwAccessibleParagraph::getAccessibleDescription()
715 SolarMutexGuard aGuard;
717 ThrowIfDisposed();
719 std::scoped_lock aGuard2( m_Mutex );
720 if( m_sDesc.isEmpty() )
721 m_sDesc = GetDescription();
723 return m_sDesc;
726 lang::Locale SAL_CALL SwAccessibleParagraph::getLocale()
728 SolarMutexGuard aGuard;
730 const SwTextFrame *pTextFrame = GetFrame()->DynCastTextFrame();
731 if( !pTextFrame )
733 throw uno::RuntimeException(u"no SwTextFrame"_ustr, getXWeak());
736 lang::Locale aLoc(g_pBreakIt->GetLocale(pTextFrame->GetLangOfChar(TextFrameIndex(0), 0, true)));
738 return aLoc;
741 // #i27138# - paragraphs are in relation CONTENT_FLOWS_FROM and/or CONTENT_FLOWS_TO
742 uno::Reference<XAccessibleRelationSet> SAL_CALL SwAccessibleParagraph::getAccessibleRelationSet()
744 SolarMutexGuard aGuard;
746 ThrowIfDisposed();
748 rtl::Reference<utl::AccessibleRelationSetHelper> pHelper = new utl::AccessibleRelationSetHelper();
750 const SwTextFrame* pTextFrame = GetFrame()->DynCastTextFrame();
751 OSL_ENSURE( pTextFrame,
752 "<SwAccessibleParagraph::getAccessibleRelationSet()> - missing text frame");
753 if ( pTextFrame )
755 const SwContentFrame* pPrevContentFrame( pTextFrame->FindPrevCnt() );
756 if ( pPrevContentFrame )
758 uno::Sequence<uno::Reference<XAccessible>> aSequence { GetMap()->GetContext(pPrevContentFrame) };
759 AccessibleRelation aAccRel(AccessibleRelationType_CONTENT_FLOWS_FROM, aSequence);
760 pHelper->AddRelation( aAccRel );
763 const SwContentFrame* pNextContentFrame( pTextFrame->FindNextCnt( true ) );
764 if ( pNextContentFrame )
766 uno::Sequence<uno::Reference<XAccessible>> aSequence { GetMap()->GetContext(pNextContentFrame) };
767 AccessibleRelation aAccRel(AccessibleRelationType_CONTENT_FLOWS_TO, aSequence);
768 pHelper->AddRelation( aAccRel );
772 return pHelper;
775 void SAL_CALL SwAccessibleParagraph::grabFocus()
777 SolarMutexGuard aGuard;
779 ThrowIfDisposed();
781 // get cursor shell
782 SwCursorShell *pCursorSh = GetCursorShell();
783 SwPaM *pCursor = GetCursor( false ); // #i27301# - consider new method signature
784 const SwTextFrame* pTextFrame = GetTextFrame();
786 if (pCursorSh != nullptr &&
787 ( pCursor == nullptr ||
788 !sw::FrameContainsNode(*pTextFrame, pCursor->GetPoint()->GetNodeIndex()) ||
789 !pTextFrame->IsInside(pTextFrame->MapModelToViewPos(*pCursor->GetPoint()))))
791 // create pam for selection
792 SwPosition const aStartPos(pTextFrame->MapViewToModelPos(pTextFrame->GetOffset()));
793 SwPaM aPaM( aStartPos );
795 // set PaM at cursor shell
796 Select( aPaM );
800 // ->#i13955#
801 vcl::Window * pWindow = GetWindow();
803 if (pWindow != nullptr)
804 pWindow->GrabFocus();
805 // <-#i13955#
808 // #i71385#
809 static bool lcl_GetBackgroundColor( Color & rColor,
810 const SwFrame* pFrame,
811 SwCursorShell* pCursorSh )
813 const SvxBrushItem* pBackgroundBrush = nullptr;
814 std::optional<Color> xSectionTOXColor;
815 SwRect aDummyRect;
816 drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes;
818 if ( pFrame &&
819 pFrame->GetBackgroundBrush( aFillAttributes, pBackgroundBrush, xSectionTOXColor, aDummyRect, false, /*bConsiderTextBox=*/false ) )
821 if ( xSectionTOXColor )
823 rColor = *xSectionTOXColor;
824 return true;
826 else
828 rColor = pBackgroundBrush->GetColor();
829 return true;
832 else if ( pCursorSh )
834 rColor = pCursorSh->Imp()->GetRetoucheColor();
835 return true;
838 return false;
841 sal_Int32 SAL_CALL SwAccessibleParagraph::getForeground()
843 SolarMutexGuard g;
845 Color aBackgroundCol;
847 if ( lcl_GetBackgroundColor( aBackgroundCol, GetFrame(), GetCursorShell() ) )
849 if ( aBackgroundCol.IsDark() )
851 return sal_Int32(COL_WHITE);
853 else
855 return sal_Int32(COL_BLACK);
859 return SwAccessibleContext::getForeground();
862 sal_Int32 SAL_CALL SwAccessibleParagraph::getBackground()
864 SolarMutexGuard g;
866 Color aBackgroundCol;
868 if ( lcl_GetBackgroundColor( aBackgroundCol, GetFrame(), GetCursorShell() ) )
870 return sal_Int32(aBackgroundCol);
873 return SwAccessibleContext::getBackground();
876 OUString SAL_CALL SwAccessibleParagraph::getImplementationName()
878 return sImplementationName;
881 sal_Bool SAL_CALL SwAccessibleParagraph::supportsService(
882 const OUString& sTestServiceName)
884 return cppu::supportsService(this, sTestServiceName);
887 uno::Sequence< OUString > SAL_CALL SwAccessibleParagraph::getSupportedServiceNames()
889 return { sServiceName, sAccessibleServiceName };
892 static uno::Sequence< OUString > const & getAttributeNames()
894 static uno::Sequence< OUString > const aNames
896 // Add the font name to attribute list
897 // sorted list of strings
898 UNO_NAME_CHAR_BACK_COLOR,
899 UNO_NAME_CHAR_COLOR,
900 UNO_NAME_CHAR_CONTOURED,
901 UNO_NAME_CHAR_EMPHASIS,
902 UNO_NAME_CHAR_ESCAPEMENT,
903 UNO_NAME_CHAR_FONT_NAME,
904 UNO_NAME_CHAR_HEIGHT,
905 UNO_NAME_CHAR_POSTURE,
906 UNO_NAME_CHAR_SHADOWED,
907 UNO_NAME_CHAR_STRIKEOUT,
908 UNO_NAME_CHAR_UNDERLINE,
909 UNO_NAME_CHAR_UNDERLINE_COLOR,
910 UNO_NAME_CHAR_WEIGHT,
912 return aNames;
915 static uno::Sequence< OUString > const & getSupplementalAttributeNames()
917 static uno::Sequence< OUString > const aNames
919 // sorted list of strings
920 UNO_NAME_NUMBERING_LEVEL,
921 UNO_NAME_NUMBERING,
922 UNO_NAME_NUMBERING_RULES,
923 UNO_NAME_PARA_ADJUST,
924 UNO_NAME_PARA_BOTTOM_MARGIN,
925 UNO_NAME_PARA_FIRST_LINE_INDENT,
926 UNO_NAME_PARA_LEFT_MARGIN,
927 UNO_NAME_PARA_LINE_SPACING,
928 UNO_NAME_PARA_RIGHT_MARGIN,
929 UNO_NAME_TABSTOPS,
931 return aNames;
934 // XAccessibleText
936 sal_Int32 SwAccessibleParagraph::getCaretPosition()
938 SolarMutexGuard aGuard;
940 ThrowIfDisposed();
942 sal_Int32 nRet = GetCaretPos();
944 std::scoped_lock aOldCaretPosGuard( m_Mutex );
945 OSL_ENSURE( nRet == m_nOldCaretPos, "caret pos out of sync" );
946 m_nOldCaretPos = nRet;
948 if( -1 != nRet )
950 ::rtl::Reference < SwAccessibleContext > xThis( this );
951 GetMap()->SetCursorContext( xThis );
954 return nRet;
957 sal_Bool SAL_CALL SwAccessibleParagraph::setCaretPosition( sal_Int32 nIndex )
959 SolarMutexGuard aGuard;
961 ThrowIfDisposed();
963 // parameter checking
964 sal_Int32 nLength = GetString().getLength();
965 if ( ! IsValidPosition( nIndex, nLength ) )
967 throw lang::IndexOutOfBoundsException();
970 bool bRet = false;
972 // get cursor shell
973 SwCursorShell* pCursorShell = GetCursorShell();
974 if( pCursorShell != nullptr )
976 // create pam for selection
977 const SwTextFrame* const pFrame = GetTextFrame();
978 TextFrameIndex const nFrameIndex(GetPortionData().GetCoreViewPosition(nIndex));
979 SwPosition aStartPos(pFrame->MapViewToModelPos(nFrameIndex));
980 SwPaM aPaM( aStartPos );
982 // set PaM at cursor shell
983 bRet = Select( aPaM );
986 return bRet;
989 sal_Unicode SwAccessibleParagraph::getCharacter( sal_Int32 nIndex )
991 SolarMutexGuard aGuard;
993 ThrowIfDisposed();
995 OUString sText( GetString() );
997 // return character (if valid)
998 if( !IsValidChar(nIndex, sText.getLength() ) )
999 throw lang::IndexOutOfBoundsException();
1001 return sText[nIndex];
1004 css::uno::Sequence< css::style::TabStop > SwAccessibleParagraph::GetCurrentTabStop( sal_Int32 nIndex )
1006 SolarMutexGuard aGuard;
1008 ThrowIfDisposed();
1010 /* #i12332# The position after the string needs special treatment.
1011 IsValidChar -> IsValidPosition
1013 if( ! (IsValidPosition( nIndex, GetString().getLength() ) ) )
1014 throw lang::IndexOutOfBoundsException();
1016 /* #i12332# */
1017 bool bBehindText = false;
1018 if ( nIndex == GetString().getLength() )
1019 bBehindText = true;
1021 // get model position & prepare GetCharRect() arguments
1022 SwCursorMoveState aMoveState;
1023 aMoveState.m_bRealHeight = true;
1024 aMoveState.m_bRealWidth = true;
1025 SwSpecialPos aSpecialPos;
1026 const SwTextFrame* const pFrame = GetTextFrame();
1028 /* #i12332# FillSpecialPos does not accept nIndex ==
1029 GetString().getLength(). In that case nPos is set to the
1030 length of the string in the core. This way GetCharRect
1031 returns the rectangle for a cursor at the end of the
1032 paragraph. */
1033 const TextFrameIndex nPos = bBehindText
1034 ? TextFrameIndex(pFrame->GetText().getLength())
1035 : GetPortionData().FillSpecialPos(nIndex, aSpecialPos, aMoveState.m_pSpecialPos );
1037 // call GetCharRect
1038 SwRect aCoreRect;
1039 SwPosition aPosition(pFrame->MapViewToModelPos(nPos));
1040 GetFrame()->GetCharRect( aCoreRect, aPosition, &aMoveState );
1042 // already get the caret position
1043 css::uno::Sequence< css::style::TabStop > tabs;
1044 const sal_Int32 nStrLen = pFrame->GetText().getLength();
1045 if( nStrLen > 0 )
1047 SwFrame* pTFrame = const_cast<SwFrame*>(GetFrame());
1048 tabs = pTFrame->GetTabStopInfo(aCoreRect.Left());
1051 if( tabs.hasElements() )
1053 // translate core coordinates into accessibility coordinates
1054 vcl::Window *pWin = GetWindow();
1055 if (!pWin)
1057 throw uno::RuntimeException(u"no Window"_ustr, getXWeak());
1060 SwRect aTmpRect(0, 0, tabs[0].Position, 0);
1062 tools::Rectangle aScreenRect( GetMap()->CoreToPixel( aTmpRect ));
1063 SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root
1065 Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds ).TopLeft() );
1066 aScreenRect.Move( -aFramePixPos.X(), -aFramePixPos.Y() );
1068 tabs.getArray()[0].Position = aScreenRect.GetWidth();
1071 return tabs;
1074 namespace {
1076 struct IndexCompare
1078 const PropertyValue* pValues;
1079 explicit IndexCompare( const PropertyValue* pVals ) : pValues(pVals) {}
1080 bool operator() ( sal_Int32 a, sal_Int32 b ) const
1082 return (pValues[a].Name < pValues[b].Name);
1088 OUString SwAccessibleParagraph::GetFieldTypeNameAtIndex(sal_Int32 nIndex)
1090 OUString strTypeName;
1091 SwFieldMgr aMgr;
1092 SwTextField* pTextField = nullptr;
1093 sal_Int32 nFieldIndex = GetPortionData().GetFieldIndex(nIndex);
1094 if (nFieldIndex >= 0)
1096 const SwTextFrame* const pFrame = GetTextFrame();
1097 sw::MergedAttrIter iter(*pFrame);
1098 while (SwTextAttr const*const pHt = iter.NextAttr())
1100 if ((pHt->Which() == RES_TXTATR_FIELD
1101 || pHt->Which() == RES_TXTATR_ANNOTATION
1102 || pHt->Which() == RES_TXTATR_INPUTFIELD)
1103 && (nFieldIndex-- == 0))
1105 pTextField = const_cast<SwTextField*>(
1106 static_txtattr_cast<SwTextField const*>(pHt));
1107 break;
1109 else if (pHt->Which() == RES_TXTATR_REFMARK
1110 && (nFieldIndex-- == 0))
1112 strTypeName = "set reference";
1116 if (pTextField)
1118 const SwField* pField = pTextField->GetFormatField().GetField();
1119 if (pField)
1121 strTypeName = SwFieldType::GetTypeStr(pField->GetTypeId());
1122 const SwFieldIds nWhich = pField->GetTyp()->Which();
1123 OUString sEntry;
1124 sal_uInt32 subType = 0;
1125 switch (nWhich)
1127 case SwFieldIds::DocStat:
1128 subType = static_cast<const SwDocStatField*>(pField)->GetSubType();
1129 break;
1130 case SwFieldIds::GetRef:
1132 switch( pField->GetSubType() )
1134 case REF_BOOKMARK:
1136 const SwGetRefField* pRefField = dynamic_cast<const SwGetRefField*>(pField);
1137 if ( pRefField && pRefField->IsRefToHeadingCrossRefBookmark() )
1138 sEntry = "Headings";
1139 else if ( pRefField && pRefField->IsRefToNumItemCrossRefBookmark() )
1140 sEntry = "Numbered Paragraphs";
1141 else
1142 sEntry = "Bookmarks";
1144 break;
1145 case REF_FOOTNOTE:
1146 sEntry = "Footnotes";
1147 break;
1148 case REF_ENDNOTE:
1149 sEntry = "Endnotes";
1150 break;
1151 case REF_SETREFATTR:
1152 sEntry = "Insert Reference";
1153 break;
1154 case REF_SEQUENCEFLD:
1155 sEntry = static_cast<const SwGetRefField*>(pField)->GetSetRefName();
1156 break;
1157 case REF_STYLE:
1158 sEntry = "StyleRef";
1159 break;
1161 //Get format string
1162 strTypeName = sEntry;
1163 // <pField->GetFormat() >= 0> is always true as <pField->GetFormat()> is unsigned
1164 // if (pField->GetFormat() >= 0)
1166 sEntry = aMgr.GetFormatStr( pField->GetTypeId(), pField->GetFormat() );
1167 if (sEntry.getLength() > 0)
1169 strTypeName += "-" + sEntry;
1173 break;
1174 case SwFieldIds::DateTime:
1175 subType = static_cast<const SwDateTimeField*>(pField)->GetSubType();
1176 break;
1177 case SwFieldIds::JumpEdit:
1179 const sal_uInt32 nFormat= pField->GetFormat();
1180 const sal_uInt16 nSize = aMgr.GetFormatCount(pField->GetTypeId(), false);
1181 if (nFormat < nSize)
1183 sEntry = aMgr.GetFormatStr(pField->GetTypeId(), nFormat);
1184 if (sEntry.getLength() > 0)
1186 strTypeName += "-" + sEntry;
1190 break;
1191 case SwFieldIds::ExtUser:
1192 subType = static_cast<const SwExtUserField*>(pField)->GetSubType();
1193 break;
1194 case SwFieldIds::HiddenText:
1195 case SwFieldIds::SetExp:
1197 sEntry = pField->GetTyp()->GetName();
1198 if (sEntry.getLength() > 0)
1200 strTypeName += "-" + sEntry;
1203 break;
1204 case SwFieldIds::DocInfo:
1205 subType = pField->GetSubType();
1206 subType &= 0x00ff;
1207 break;
1208 case SwFieldIds::RefPageSet:
1210 const SwRefPageSetField* pRPld = static_cast<const SwRefPageSetField*>(pField);
1211 bool bOn = pRPld->IsOn();
1212 strTypeName += "-";
1213 if (bOn)
1214 strTypeName += "on";
1215 else
1216 strTypeName += "off";
1218 break;
1219 case SwFieldIds::Author:
1221 strTypeName += "-" + aMgr.GetFormatStr(pField->GetTypeId(), pField->GetFormat() & 0xff);
1223 break;
1224 default: break;
1226 if (subType > 0 || nWhich == SwFieldIds::DocInfo || nWhich == SwFieldIds::ExtUser || nWhich == SwFieldIds::DocStat)
1228 std::vector<OUString> aLst;
1229 aMgr.GetSubTypes(pField->GetTypeId(), aLst);
1230 if (subType < aLst.size())
1231 sEntry = aLst[subType];
1232 if (sEntry.getLength() > 0)
1234 if (nWhich == SwFieldIds::DocInfo)
1236 strTypeName = sEntry;
1237 sal_uInt16 nSize = aMgr.GetFormatCount(pField->GetTypeId(), false);
1238 const sal_uInt16 nExSub = pField->GetSubType() & 0xff00;
1239 if (nSize > 0 && nExSub > 0)
1241 //Get extra subtype string
1242 strTypeName += "-";
1243 sEntry = aMgr.GetFormatStr(pField->GetTypeId(), nExSub/0x0100-1);
1244 strTypeName += sEntry;
1247 else
1249 strTypeName += "-" + sEntry;
1255 return strTypeName;
1258 // #i63870# - re-implement method on behalf of methods
1259 // <_getDefaultAttributesImpl(..)> and <_getRunAttributesImpl(..)>
1260 uno::Sequence<PropertyValue> SwAccessibleParagraph::getCharacterAttributes(
1261 sal_Int32 nIndex,
1262 const uno::Sequence< OUString >& aRequestedAttributes )
1265 SolarMutexGuard aGuard;
1267 ThrowIfDisposed();
1269 const OUString& rText = GetString();
1271 if (!IsValidPosition(nIndex, rText.getLength()))
1272 throw lang::IndexOutOfBoundsException();
1274 bool bSupplementalMode = false;
1275 uno::Sequence< OUString > aNames = aRequestedAttributes;
1276 if (!aNames.hasElements())
1278 bSupplementalMode = true;
1279 aNames = getAttributeNames();
1281 // retrieve default character attributes
1282 tAccParaPropValMap aDefAttrSeq;
1283 _getDefaultAttributesImpl( aNames, aDefAttrSeq, true );
1285 // retrieved run character attributes
1286 tAccParaPropValMap aRunAttrSeq;
1287 _getRunAttributesImpl( nIndex, aNames, aRunAttrSeq );
1289 // this allows to request one or more supplemental attributes, only
1290 bSupplementalMode = bSupplementalMode || aDefAttrSeq.empty() || aRunAttrSeq.empty();
1292 // merge default and run attributes
1293 std::vector< PropertyValue > aValues( aDefAttrSeq.size() );
1294 sal_Int32 i = 0;
1295 for ( const auto& rDefEntry : aDefAttrSeq )
1297 tAccParaPropValMap::const_iterator aRunIter =
1298 aRunAttrSeq.find( rDefEntry.first );
1299 if ( aRunIter != aRunAttrSeq.end() )
1301 aValues[i] = aRunIter->second;
1303 else
1305 aValues[i] = rDefEntry.second;
1307 ++i;
1309 if( bSupplementalMode )
1311 uno::Sequence< OUString > aSupplementalNames = aRequestedAttributes;
1312 if (!aSupplementalNames.hasElements())
1313 aSupplementalNames = getSupplementalAttributeNames();
1315 tAccParaPropValMap aSupplementalAttrSeq;
1316 _getSupplementalAttributesImpl( aSupplementalNames, aSupplementalAttrSeq );
1318 aValues.resize( aValues.size() + aSupplementalAttrSeq.size() );
1320 for ( const auto& rSupplementalEntry : aSupplementalAttrSeq )
1322 aValues[i] = rSupplementalEntry.second;
1323 ++i;
1326 _correctValues( nIndex, aValues );
1328 aValues.emplace_back();
1330 OUString strTypeName = GetFieldTypeNameAtIndex(nIndex);
1331 if (!strTypeName.isEmpty())
1333 aValues.emplace_back();
1334 PropertyValue& rValueFT = aValues.back();
1335 rValueFT.Name = "FieldType";
1336 rValueFT.Value <<= strTypeName.toAsciiLowerCase();
1337 rValueFT.Handle = -1;
1338 rValueFT.State = PropertyState_DIRECT_VALUE;
1341 //sort property values
1342 // build sorted index array
1343 sal_Int32 nLength = aValues.size();
1344 std::vector<sal_Int32> aIndices;
1345 aIndices.reserve(nLength);
1346 for (i = 0; i < nLength; ++i)
1347 aIndices.push_back(i);
1348 std::sort(aIndices.begin(), aIndices.end(), IndexCompare(aValues.data()));
1349 // create sorted sequences according to index array
1350 uno::Sequence<PropertyValue> aNewValues( nLength );
1351 PropertyValue* pNewValues = aNewValues.getArray();
1352 for (i = 0; i < nLength; ++i)
1354 pNewValues[i] = aValues[aIndices[i]];
1356 return aNewValues;
1359 return comphelper::containerToSequence(aValues);
1362 static void SetPutRecursive(SfxItemSet &targetSet, const SfxItemSet &sourceSet)
1364 const SfxItemSet *const pParentSet = sourceSet.GetParent();
1365 if (pParentSet)
1366 SetPutRecursive(targetSet, *pParentSet);
1367 targetSet.Put(sourceSet);
1370 // #i63870#
1371 void SwAccessibleParagraph::_getDefaultAttributesImpl(
1372 const uno::Sequence< OUString >& aRequestedAttributes,
1373 tAccParaPropValMap& rDefAttrSeq,
1374 const bool bOnlyCharAttrs )
1376 // retrieve default attributes
1377 const SwTextFrame* const pFrame = GetTextFrame();
1378 const SwTextNode *const pTextNode(pFrame->GetTextNodeForParaProps());
1379 std::optional<SfxItemSet> pSet;
1380 if ( !bOnlyCharAttrs )
1382 pSet.emplace( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()),
1383 svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
1384 RES_PARATR_BEGIN, RES_PARATR_END - 1,
1385 RES_FRMATR_BEGIN, RES_FRMATR_END - 1> );
1387 else
1389 pSet.emplace( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()),
1390 svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END - 1> );
1392 // #i82637# - From the perspective of the a11y API the default character
1393 // attributes are the character attributes, which are set at the paragraph style
1394 // of the paragraph. The character attributes set at the automatic paragraph
1395 // style of the paragraph are treated as run attributes.
1396 // pTextNode->SwContentNode::GetAttr( *pSet );
1397 // get default paragraph attributes, if needed, and merge these into <pSet>
1398 if ( !bOnlyCharAttrs )
1400 SfxItemSetFixed<RES_PARATR_BEGIN, RES_PARATR_END - 1,
1401 RES_FRMATR_BEGIN, RES_FRMATR_END - 1>
1402 aParaSet( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()) );
1403 pTextNode->SwContentNode::GetAttr( aParaSet );
1404 pSet->Put( aParaSet );
1406 // get default character attributes and merge these into <pSet>
1407 OSL_ENSURE( pTextNode->GetTextColl(),
1408 "<SwAccessibleParagraph::_getDefaultAttributesImpl(..)> - missing paragraph style. Serious defect!" );
1409 if ( pTextNode->GetTextColl() )
1411 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END - 1>
1412 aCharSet( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()) );
1413 SetPutRecursive( aCharSet, pTextNode->GetTextColl()->GetAttrSet() );
1414 pSet->Put( aCharSet );
1417 // build-up sequence containing the run attributes <rDefAttrSeq>
1418 tAccParaPropValMap aDefAttrSeq;
1420 const SfxItemPropertyMap& rPropMap =
1421 aSwMapProvider.GetPropertySet( PROPERTY_MAP_TEXT_CURSOR )->getPropertyMap();
1422 for ( const auto pEntry : rPropMap.getPropertyEntries() )
1424 const SfxPoolItem* pItem = pSet->GetItem( pEntry->nWID );
1425 if ( pItem )
1427 uno::Any aVal;
1428 pItem->QueryValue( aVal, pEntry->nMemberId );
1430 PropertyValue rPropVal;
1431 rPropVal.Name = pEntry->aName;
1432 rPropVal.Value = std::move(aVal);
1433 rPropVal.Handle = -1;
1434 rPropVal.State = beans::PropertyState_DEFAULT_VALUE;
1436 aDefAttrSeq[rPropVal.Name] = rPropVal;
1440 // #i72800#
1441 // add property value entry for the paragraph style
1442 if ( !bOnlyCharAttrs && pTextNode->GetTextColl() )
1444 if ( aDefAttrSeq.find( UNO_NAME_PARA_STYLE_NAME ) == aDefAttrSeq.end() )
1446 PropertyValue rPropVal;
1447 rPropVal.Name = UNO_NAME_PARA_STYLE_NAME;
1448 rPropVal.Value <<= pTextNode->GetTextColl()->GetName();
1449 rPropVal.Handle = -1;
1450 rPropVal.State = beans::PropertyState_DEFAULT_VALUE;
1452 aDefAttrSeq[rPropVal.Name] = rPropVal;
1456 // #i73371#
1457 // resolve value text::WritingMode2::PAGE of property value entry WritingMode
1458 if ( !bOnlyCharAttrs && GetFrame() )
1460 tAccParaPropValMap::iterator aIter = aDefAttrSeq.find( UNO_NAME_WRITING_MODE );
1461 if ( aIter != aDefAttrSeq.end() )
1463 PropertyValue rPropVal( aIter->second );
1464 sal_Int16 nVal = rPropVal.Value.get<sal_Int16>();
1465 if ( nVal == text::WritingMode2::PAGE )
1467 const SwFrame* pUpperFrame( GetFrame()->GetUpper() );
1468 while ( pUpperFrame )
1470 if ( pUpperFrame->GetType() &
1471 ( SwFrameType::Page | SwFrameType::Fly | SwFrameType::Section | SwFrameType::Tab | SwFrameType::Cell ) )
1473 if ( pUpperFrame->IsVertical() )
1475 nVal = text::WritingMode2::TB_RL;
1477 else if ( pUpperFrame->IsRightToLeft() )
1479 nVal = text::WritingMode2::RL_TB;
1481 else
1483 nVal = text::WritingMode2::LR_TB;
1485 rPropVal.Value <<= nVal;
1486 aDefAttrSeq[rPropVal.Name] = rPropVal;
1487 break;
1490 if ( pUpperFrame->IsFlyFrame() )
1492 pUpperFrame = static_cast<const SwFlyFrame*>(pUpperFrame)->GetAnchorFrame();
1494 else
1496 pUpperFrame = pUpperFrame->GetUpper();
1504 if ( !aRequestedAttributes.hasElements() )
1506 rDefAttrSeq = std::move(aDefAttrSeq);
1508 else
1510 for( const OUString& rReqAttr : aRequestedAttributes )
1512 tAccParaPropValMap::const_iterator const aIter = aDefAttrSeq.find( rReqAttr );
1513 if ( aIter != aDefAttrSeq.end() )
1515 rDefAttrSeq[ aIter->first ] = aIter->second;
1521 uno::Sequence< PropertyValue > SwAccessibleParagraph::getDefaultAttributes(
1522 const uno::Sequence< OUString >& aRequestedAttributes )
1524 SolarMutexGuard aGuard;
1526 ThrowIfDisposed();
1528 tAccParaPropValMap aDefAttrSeq;
1529 _getDefaultAttributesImpl( aRequestedAttributes, aDefAttrSeq );
1531 // #i92233#
1532 static constexpr OUString sMMToPixelRatio = u"MMToPixelRatio"_ustr;
1533 bool bProvideMMToPixelRatio( !aRequestedAttributes.hasElements() ||
1534 (comphelper::findValue(aRequestedAttributes, sMMToPixelRatio) != -1) );
1536 uno::Sequence< PropertyValue > aValues( aDefAttrSeq.size() +
1537 ( bProvideMMToPixelRatio ? 1 : 0 ) );
1538 auto pValues = aValues.getArray();
1539 std::transform(aDefAttrSeq.begin(), aDefAttrSeq.end(), pValues,
1540 [](const auto& rEntry) -> PropertyValue { return rEntry.second; });
1542 // #i92233#
1543 if ( bProvideMMToPixelRatio )
1545 PropertyValue rPropVal;
1546 rPropVal.Name = sMMToPixelRatio;
1547 const Size a100thMMSize( 1000, 1000 );
1548 const Size aPixelSize = GetMap()->LogicToPixel( a100thMMSize );
1549 const float fRatio = (static_cast<float>(a100thMMSize.Width())/100)/aPixelSize.Width();
1550 rPropVal.Value <<= fRatio;
1551 rPropVal.Handle = -1;
1552 rPropVal.State = beans::PropertyState_DEFAULT_VALUE;
1553 pValues[ aValues.getLength() - 1 ] = std::move(rPropVal);
1556 return aValues;
1559 void SwAccessibleParagraph::_getRunAttributesImpl(
1560 const sal_Int32 nIndex,
1561 const uno::Sequence< OUString >& aRequestedAttributes,
1562 tAccParaPropValMap& rRunAttrSeq )
1564 // create PaM for character at position <nIndex>
1565 std::optional<SwPaM> pPaM;
1566 const TextFrameIndex nCorePos(GetPortionData().GetCoreViewPosition(nIndex));
1567 const SwTextFrame* const pFrame = GetTextFrame();
1568 SwPosition const aModelPos(pFrame->MapViewToModelPos(nCorePos));
1569 SwTextNode *const pTextNode(aModelPos.GetNode().GetTextNode());
1571 SwPosition const aEndPos(*pTextNode,
1572 aModelPos.GetContentIndex() == pTextNode->Len()
1573 ? pTextNode->Len() // ???
1574 : aModelPos.GetContentIndex() + 1);
1575 pPaM.emplace(aModelPos, aEndPos);
1578 // retrieve character attributes for the created PaM <pPaM>
1579 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END -1> aSet( pPaM->GetDoc().GetAttrPool() );
1580 // #i82637#
1581 // From the perspective of the a11y API the character attributes, which
1582 // are set at the automatic paragraph style of the paragraph, are treated
1583 // as run attributes.
1584 // SwXTextCursor::GetCursorAttr( *pPaM, aSet, sal_True, sal_True );
1585 // get character attributes from automatic paragraph style and merge these into <aSet>
1587 if ( pTextNode->HasSwAttrSet() )
1589 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END -1> aAutomaticParaStyleCharAttrs( pPaM->GetDoc().GetAttrPool());
1590 aAutomaticParaStyleCharAttrs.Put( *(pTextNode->GetpSwAttrSet()), false );
1591 aSet.Put( aAutomaticParaStyleCharAttrs );
1594 // get character attributes at <pPaM> and merge these into <aSet>
1596 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END -1> aCharAttrsAtPaM( pPaM->GetDoc().GetAttrPool() );
1597 SwUnoCursorHelper::GetCursorAttr(*pPaM, aCharAttrsAtPaM, true);
1598 aSet.Put( aCharAttrsAtPaM );
1601 // build-up sequence containing the run attributes <rRunAttrSeq>
1603 tAccParaPropValMap aRunAttrSeq;
1605 tAccParaPropValMap aDefAttrSeq;
1606 uno::Sequence< OUString > aDummy;
1607 _getDefaultAttributesImpl( aDummy, aDefAttrSeq, true ); // #i82637#
1609 const SfxItemPropertyMap& rPropMap =
1610 aSwMapProvider.GetPropertySet( PROPERTY_MAP_TEXT_CURSOR )->getPropertyMap();
1611 for ( const auto pEntry : rPropMap.getPropertyEntries() )
1613 const SfxPoolItem* pItem( nullptr );
1614 // #i82637# - Found character attributes, whose value equals the value of
1615 // the corresponding default character attributes, are excluded.
1616 if ( aSet.GetItemState( pEntry->nWID, true, &pItem ) == SfxItemState::SET )
1618 uno::Any aVal;
1619 pItem->QueryValue( aVal, pEntry->nMemberId );
1621 PropertyValue rPropVal;
1622 rPropVal.Name = pEntry->aName;
1623 rPropVal.Value = std::move(aVal);
1624 rPropVal.Handle = -1;
1625 rPropVal.State = PropertyState_DIRECT_VALUE;
1627 tAccParaPropValMap::const_iterator aDefIter =
1628 aDefAttrSeq.find( rPropVal.Name );
1629 if ( aDefIter == aDefAttrSeq.end() ||
1630 rPropVal.Value != aDefIter->second.Value )
1632 aRunAttrSeq[rPropVal.Name] = rPropVal;
1638 if ( !aRequestedAttributes.hasElements() )
1640 rRunAttrSeq = std::move(aRunAttrSeq);
1642 else
1644 for( const OUString& rReqAttr : aRequestedAttributes )
1646 tAccParaPropValMap::iterator aIter = aRunAttrSeq.find( rReqAttr );
1647 if ( aIter != aRunAttrSeq.end() )
1649 rRunAttrSeq[ (*aIter).first ] = (*aIter).second;
1656 uno::Sequence< PropertyValue > SwAccessibleParagraph::getRunAttributes(
1657 sal_Int32 nIndex,
1658 const uno::Sequence< OUString >& aRequestedAttributes )
1660 SolarMutexGuard aGuard;
1662 ThrowIfDisposed();
1665 const OUString& rText = GetString();
1666 if (!IsValidPosition(nIndex, rText.getLength()))
1668 throw lang::IndexOutOfBoundsException();
1672 tAccParaPropValMap aRunAttrSeq;
1673 _getRunAttributesImpl( nIndex, aRequestedAttributes, aRunAttrSeq );
1675 return comphelper::mapValuesToSequence( aRunAttrSeq );
1678 void SwAccessibleParagraph::_getSupplementalAttributesImpl(
1679 const uno::Sequence< OUString >& aRequestedAttributes,
1680 tAccParaPropValMap& rSupplementalAttrSeq )
1682 const SwTextFrame* const pFrame = GetTextFrame();
1683 const SwTextNode *const pTextNode(pFrame->GetTextNodeForParaProps());
1684 SfxItemSetFixed<
1685 RES_PARATR_LINESPACING, RES_PARATR_ADJUST,
1686 RES_PARATR_TABSTOP, RES_PARATR_TABSTOP,
1687 RES_PARATR_NUMRULE, RES_PARATR_NUMRULE,
1688 RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END - 1,
1689 RES_MARGIN_FIRSTLINE, RES_MARGIN_RIGHT,
1690 RES_UL_SPACE, RES_UL_SPACE>
1691 aSet( const_cast<SwAttrPool&>(pTextNode->GetDoc().GetAttrPool()) );
1693 if ( pTextNode->HasBullet() || pTextNode->HasNumber() )
1695 aSet.Put( pTextNode->GetAttr(RES_PARATR_LIST_LEVEL) );
1696 aSet.Put( pTextNode->GetAttr(RES_PARATR_LIST_ISCOUNTED) );
1698 aSet.Put( pTextNode->SwContentNode::GetAttr(RES_UL_SPACE) );
1699 aSet.Put( pTextNode->SwContentNode::GetAttr(RES_MARGIN_FIRSTLINE) );
1700 aSet.Put( pTextNode->SwContentNode::GetAttr(RES_MARGIN_TEXTLEFT) );
1701 aSet.Put( pTextNode->SwContentNode::GetAttr(RES_MARGIN_RIGHT) );
1702 aSet.Put( pTextNode->SwContentNode::GetAttr(RES_PARATR_ADJUST) );
1704 tAccParaPropValMap aSupplementalAttrSeq;
1706 std::span<const SfxItemPropertyMapEntry> pPropMap(
1707 aSwMapProvider.GetPropertyMapEntries( PROPERTY_MAP_ACCESSIBILITY_TEXT_ATTRIBUTE ) );
1708 for (const auto & rEntry : pPropMap)
1710 // For a paragraph, list level property is not set but when queried the returned default
1711 // value is 0, exactly the same value of top level list item; that prevents using
1712 // list level property for discerning simple paragraph from list item;
1713 // the following check allows not to return the list level property at all
1714 // when we are dealing with a simple paragraph
1715 if ((rEntry.nWID == RES_PARATR_LIST_LEVEL || rEntry.nWID == RES_PARATR_LIST_ISCOUNTED) &&
1716 !aSet.HasItem( rEntry.nWID ))
1717 continue;
1719 const SfxPoolItem* pItem = aSet.GetItem( rEntry.nWID );
1720 if ( pItem )
1722 uno::Any aVal;
1723 pItem->QueryValue( aVal, rEntry.nMemberId );
1725 PropertyValue rPropVal;
1726 rPropVal.Name = rEntry.aName;
1727 rPropVal.Value = std::move(aVal);
1728 rPropVal.Handle = -1;
1729 rPropVal.State = beans::PropertyState_DEFAULT_VALUE;
1731 aSupplementalAttrSeq[rPropVal.Name] = rPropVal;
1736 for( const OUString& rSupplementalAttr : aRequestedAttributes )
1738 tAccParaPropValMap::const_iterator const aIter = aSupplementalAttrSeq.find( rSupplementalAttr );
1739 if ( aIter != aSupplementalAttrSeq.end() )
1741 rSupplementalAttrSeq[ aIter->first ] = aIter->second;
1746 void SwAccessibleParagraph::_correctValues( const sal_Int32 nIndex,
1747 std::vector< PropertyValue >& rValues)
1749 PropertyValue ChangeAttr, ChangeAttrColor;
1751 const SwRangeRedline* pRedline = GetRedlineAtIndex();
1752 if ( pRedline )
1755 const SwModuleOptions* pOpt = SwModule::get()->GetModuleConfig();
1756 AuthorCharAttr aChangeAttr;
1757 if ( pOpt )
1759 switch( pRedline->GetType())
1761 case RedlineType::Insert:
1762 aChangeAttr = pOpt->GetInsertAuthorAttr();
1763 break;
1764 case RedlineType::Delete:
1765 aChangeAttr = pOpt->GetDeletedAuthorAttr();
1766 break;
1767 case RedlineType::Format:
1768 aChangeAttr = pOpt->GetFormatAuthorAttr();
1769 break;
1770 default: break;
1773 switch( aChangeAttr.m_nItemId )
1775 case SID_ATTR_CHAR_WEIGHT:
1776 ChangeAttr.Name = UNO_NAME_CHAR_WEIGHT;
1777 ChangeAttr.Value <<= awt::FontWeight::BOLD;
1778 break;
1779 case SID_ATTR_CHAR_POSTURE:
1780 ChangeAttr.Name = UNO_NAME_CHAR_POSTURE;
1781 ChangeAttr.Value <<= awt::FontSlant_ITALIC; //char posture
1782 break;
1783 case SID_ATTR_CHAR_STRIKEOUT:
1784 ChangeAttr.Name = UNO_NAME_CHAR_STRIKEOUT;
1785 ChangeAttr.Value <<= awt::FontStrikeout::SINGLE; //char strikeout
1786 break;
1787 case SID_ATTR_CHAR_UNDERLINE:
1788 ChangeAttr.Name = UNO_NAME_CHAR_UNDERLINE;
1789 ChangeAttr.Value <<= aChangeAttr.m_nAttr; //underline line
1790 break;
1792 if( aChangeAttr.m_nColor != COL_NONE_COLOR )
1794 if( aChangeAttr.m_nItemId == SID_ATTR_BRUSH )
1796 ChangeAttrColor.Name = UNO_NAME_CHAR_BACK_COLOR;
1797 if( aChangeAttr.m_nColor == COL_TRANSPARENT )//char backcolor
1798 ChangeAttrColor.Value <<= COL_BLUE;
1799 else
1800 ChangeAttrColor.Value <<= aChangeAttr.m_nColor;
1802 else
1804 ChangeAttrColor.Name = UNO_NAME_CHAR_COLOR;
1805 if( aChangeAttr.m_nColor == COL_TRANSPARENT )//char color
1806 ChangeAttrColor.Value <<= COL_BLUE;
1807 else
1808 ChangeAttrColor.Value <<= aChangeAttr.m_nColor;
1813 // sw_redlinehide: this function only needs SwWrongList for 1 character,
1814 // and the end is excluded by InWrongWord(),
1815 // so it ought to work to just pick the wrong-list/node that contains
1816 // the character following the given nIndex
1817 const SwTextFrame* const pFrame = GetTextFrame();
1818 TextFrameIndex const nCorePos(GetPortionData().GetCoreViewPosition(nIndex));
1819 std::pair<SwTextNode*, sal_Int32> pos(pFrame->MapViewToModel(nCorePos));
1820 if (pos.first->Len() == pos.second
1821 && nCorePos != TextFrameIndex(pFrame->GetText().getLength()))
1823 pos = pFrame->MapViewToModel(nCorePos + TextFrameIndex(1)); // try this one instead
1824 assert(pos.first->Len() != pos.second);
1827 sal_Int32 nValues = rValues.size();
1828 for (sal_Int32 i = 0; i < nValues; ++i)
1830 PropertyValue& rValue = rValues[i];
1832 if (rValue.Name == ChangeAttr.Name )
1834 rValue.Value = ChangeAttr.Value;
1835 continue;
1838 if (rValue.Name == ChangeAttrColor.Name )
1840 rValue.Value = ChangeAttrColor.Value;
1841 continue;
1844 //back color
1845 if (rValue.Name == UNO_NAME_CHAR_BACK_COLOR)
1847 uno::Any &anyChar = rValue.Value;
1848 Color backColor;
1849 anyChar >>= backColor;
1850 if (COL_AUTO == backColor)
1852 uno::Reference<XAccessibleComponent> xComponent(this);
1853 if (xComponent.is())
1855 sal_uInt32 crBack = static_cast<sal_uInt32>(xComponent->getBackground());
1856 rValue.Value <<= crBack;
1859 continue;
1862 //char color
1863 if (rValue.Name == UNO_NAME_CHAR_COLOR)
1865 if( GetPortionData().IsInGrayPortion( nIndex ) )
1866 rValue.Value <<= GetCursorShell()->GetViewOptions()->GetFieldShadingsColor();
1867 uno::Any &anyChar = rValue.Value;
1868 Color charColor;
1869 anyChar >>= charColor;
1871 if( COL_AUTO == charColor )
1873 uno::Reference<XAccessibleComponent> xComponent(this);
1874 if (xComponent.is())
1876 Color cr(ColorTransparency, xComponent->getBackground());
1877 sal_uInt32 crChar = sal_uInt32(cr.IsDark() ? COL_WHITE : COL_BLACK);
1878 rValue.Value <<= crChar;
1881 continue;
1884 // UnderLineColor
1885 if (rValue.Name == UNO_NAME_CHAR_UNDERLINE_COLOR)
1887 uno::Any &anyChar = rValue.Value;
1888 Color underlineColor;
1889 anyChar >>= underlineColor;
1890 if ( COL_AUTO == underlineColor )
1892 uno::Reference<XAccessibleComponent> xComponent(this);
1893 if (xComponent.is())
1895 Color cr(ColorTransparency, xComponent->getBackground());
1896 underlineColor = cr.IsDark() ? COL_WHITE : COL_BLACK;
1897 rValue.Value <<= underlineColor;
1901 continue;
1904 //tab stop
1905 if (rValue.Name == UNO_NAME_TABSTOPS)
1907 css::uno::Sequence< css::style::TabStop > tabs = GetCurrentTabStop( nIndex );
1908 if( !tabs.hasElements() )
1910 css::style::TabStop ts;
1911 css::awt::Rectangle rc0 = getCharacterBounds(0);
1912 css::awt::Rectangle rc1 = getCharacterBounds(nIndex);
1913 if( rc1.X - rc0.X >= 48 )
1914 ts.Position = (rc1.X - rc0.X) - (rc1.X - rc0.X - 48)% 47 + 47;
1915 else
1916 ts.Position = 48;
1917 ts.DecimalChar = ' ';
1918 ts.FillChar = ' ';
1919 ts.Alignment = css::style::TabAlign_LEFT;
1920 tabs = { ts };
1922 rValue.Value <<= tabs;
1923 continue;
1926 //footnote & endnote
1927 if (rValue.Name == UNO_NAME_CHAR_ESCAPEMENT)
1929 if ( GetPortionData().IsIndexInFootnode(nIndex) )
1931 rValue.Value <<= sal_Int32(101);
1933 continue;
1938 awt::Rectangle SwAccessibleParagraph::getCharacterBounds(
1939 sal_Int32 nIndex )
1941 SolarMutexGuard aGuard;
1943 ThrowIfDisposed();
1945 // #i12332# The position after the string needs special treatment.
1946 // IsValidChar -> IsValidPosition
1947 if( ! (IsValidPosition( nIndex, GetString().getLength() ) ) )
1948 throw lang::IndexOutOfBoundsException();
1950 // #i12332#
1951 bool bBehindText = false;
1952 if ( nIndex == GetString().getLength() )
1953 bBehindText = true;
1955 // get model position & prepare GetCharRect() arguments
1956 SwCursorMoveState aMoveState;
1957 aMoveState.m_bRealHeight = true;
1958 aMoveState.m_bRealWidth = true;
1959 SwSpecialPos aSpecialPos;
1960 const SwTextFrame* const pFrame = GetTextFrame();
1962 /** #i12332# FillSpecialPos does not accept nIndex ==
1963 GetString().getLength(). In that case nPos is set to the
1964 length of the string in the core. This way GetCharRect
1965 returns the rectangle for a cursor at the end of the
1966 paragraph. */
1967 const TextFrameIndex nPos = bBehindText
1968 ? TextFrameIndex(pFrame->GetText().getLength())
1969 : GetPortionData().FillSpecialPos(nIndex, aSpecialPos, aMoveState.m_pSpecialPos );
1971 // call GetCharRect
1972 SwRect aCoreRect;
1973 SwPosition aPosition(pFrame->MapViewToModelPos(nPos));
1974 GetFrame()->GetCharRect( aCoreRect, aPosition, &aMoveState );
1976 // translate core coordinates into accessibility coordinates
1977 vcl::Window *pWin = GetWindow();
1978 if (!pWin)
1980 throw uno::RuntimeException(u"no Window"_ustr, getXWeak());
1983 tools::Rectangle aScreenRect( GetMap()->CoreToPixel( aCoreRect ));
1984 SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root
1986 Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds ).TopLeft() );
1987 aScreenRect.Move( -aFramePixPos.getX(), -aFramePixPos.getY() );
1989 // convert into AWT Rectangle
1990 return awt::Rectangle(
1991 aScreenRect.Left(), aScreenRect.Top(),
1992 aScreenRect.GetWidth(), aScreenRect.GetHeight() );
1995 sal_Int32 SwAccessibleParagraph::getCharacterCount()
1997 SolarMutexGuard aGuard;
1999 ThrowIfDisposed();
2001 return GetString().getLength();
2004 sal_Int32 SwAccessibleParagraph::getIndexAtPoint( const awt::Point& rPoint )
2006 SolarMutexGuard aGuard;
2008 ThrowIfDisposed();
2010 // construct Point (translate into layout coordinates)
2011 vcl::Window *pWin = GetWindow();
2012 if (!pWin)
2014 throw uno::RuntimeException(u"no Window"_ustr, getXWeak());
2016 Point aPoint( rPoint.X, rPoint.Y );
2017 SwRect aLogBounds( GetBounds( *(GetMap()), GetFrame() ) ); // twip rel to doc root
2018 Point aPixPos( GetMap()->CoreToPixel( aLogBounds ).TopLeft() );
2019 aPoint.setX(aPoint.getX() + aPixPos.getX());
2020 aPoint.setY(aPoint.getY() + aPixPos.getY());
2021 Point aCorePoint( GetMap()->PixelToCore( aPoint ) );
2022 if( !aLogBounds.Contains( aCorePoint ) )
2024 // #i12332# rPoint is may also be in rectangle returned by
2025 // getCharacterBounds(getCharacterCount()
2027 awt::Rectangle aRectEndPos =
2028 getCharacterBounds(getCharacterCount());
2030 if (rPoint.X - aRectEndPos.X >= 0 &&
2031 rPoint.X - aRectEndPos.X < aRectEndPos.Width &&
2032 rPoint.Y - aRectEndPos.Y >= 0 &&
2033 rPoint.Y - aRectEndPos.Y < aRectEndPos.Height)
2034 return getCharacterCount();
2036 return -1;
2039 // ask core for position
2040 OSL_ENSURE( GetFrame() != nullptr, "The text frame has vanished!" );
2041 OSL_ENSURE( GetFrame()->IsTextFrame(), "The text frame has mutated!" );
2042 const SwTextFrame* pFrame = GetTextFrame();
2043 // construct SwPosition (where GetModelPositionForViewPoint() will put the result into)
2044 SwTextNode* pNode = const_cast<SwTextNode*>(pFrame->GetTextNodeFirst());
2045 SwPosition aPos(*pNode, 0);
2046 SwCursorMoveState aMoveState;
2047 aMoveState.m_bPosMatchesBounds = true;
2048 const bool bSuccess = pFrame->GetModelPositionForViewPoint( &aPos, aCorePoint, &aMoveState );
2050 TextFrameIndex nIndex = pFrame->MapModelToViewPos(aPos);
2051 if (TextFrameIndex(0) < nIndex)
2053 assert(bSuccess);
2054 SwRect aResultRect;
2055 pFrame->GetCharRect( aResultRect, aPos );
2056 bool bVert = pFrame->IsVertical();
2057 bool bR2L = pFrame->IsRightToLeft();
2059 if ( (!bVert && aResultRect.Pos().getX() > aCorePoint.getX()) ||
2060 ( bVert && aResultRect.Pos().getY() > aCorePoint.getY()) ||
2061 ( bR2L && aResultRect.Right() < aCorePoint.getX()) )
2063 SwPosition aPosPrev(pFrame->MapViewToModelPos(nIndex - TextFrameIndex(1)));
2064 SwRect aResultRectPrev;
2065 pFrame->GetCharRect( aResultRectPrev, aPosPrev );
2066 if ( (!bVert && aResultRectPrev.Pos().getX() < aCorePoint.getX() && aResultRect.Pos().getY() == aResultRectPrev.Pos().getY()) ||
2067 ( bVert && aResultRectPrev.Pos().getY() < aCorePoint.getY() && aResultRect.Pos().getX() == aResultRectPrev.Pos().getX()) ||
2068 ( bR2L && aResultRectPrev.Right() > aCorePoint.getX() && aResultRect.Pos().getY() == aResultRectPrev.Pos().getY()) )
2070 --nIndex;
2075 return bSuccess
2076 ? GetPortionData().GetAccessiblePosition(nIndex)
2077 : -1;
2080 OUString SwAccessibleParagraph::getSelectedText()
2082 SolarMutexGuard aGuard;
2084 ThrowIfDisposed();
2086 sal_Int32 nStart, nEnd;
2087 bool bSelected = GetSelection( nStart, nEnd );
2088 return bSelected
2089 ? GetString().copy( nStart, nEnd - nStart )
2090 : OUString();
2093 sal_Int32 SwAccessibleParagraph::getSelectionStart()
2095 SolarMutexGuard aGuard;
2097 ThrowIfDisposed();
2099 sal_Int32 nStart, nEnd;
2100 GetSelection( nStart, nEnd );
2101 return nStart;
2104 sal_Int32 SwAccessibleParagraph::getSelectionEnd()
2106 SolarMutexGuard aGuard;
2108 ThrowIfDisposed();
2110 sal_Int32 nStart, nEnd;
2111 GetSelection( nStart, nEnd );
2112 return nEnd;
2115 sal_Bool SwAccessibleParagraph::setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
2117 SolarMutexGuard aGuard;
2119 ThrowIfDisposed();
2121 // parameter checking
2122 sal_Int32 nLength = GetString().getLength();
2123 if ( ! IsValidRange( nStartIndex, nEndIndex, nLength ) )
2125 throw lang::IndexOutOfBoundsException();
2128 bool bRet = false;
2130 // get cursor shell
2131 SwCursorShell* pCursorShell = GetCursorShell();
2132 if( pCursorShell != nullptr )
2134 // create pam for selection
2135 const SwTextFrame* const pFrame = GetTextFrame();
2136 TextFrameIndex const nStart(GetPortionData().GetCoreViewPosition(nStartIndex));
2137 TextFrameIndex const nEnd(GetPortionData().GetCoreViewPosition(nEndIndex));
2138 SwPaM aPaM(pFrame->MapViewToModelPos(nStart));
2139 aPaM.SetMark();
2140 *aPaM.GetPoint() = pFrame->MapViewToModelPos(nEnd);
2142 // set PaM at cursor shell
2143 bRet = Select( aPaM );
2146 return bRet;
2149 OUString SwAccessibleParagraph::getText()
2151 SolarMutexGuard aGuard;
2153 ThrowIfDisposed();
2155 return GetString();
2158 OUString SwAccessibleParagraph::getTextRange(
2159 sal_Int32 nStartIndex, sal_Int32 nEndIndex )
2161 SolarMutexGuard aGuard;
2163 ThrowIfDisposed();
2165 OUString sText( GetString() );
2167 if ( !IsValidRange( nStartIndex, nEndIndex, sText.getLength() ) )
2168 throw lang::IndexOutOfBoundsException();
2170 OrderRange( nStartIndex, nEndIndex );
2171 return sText.copy(nStartIndex, nEndIndex-nStartIndex );
2174 /*accessibility::*/TextSegment SwAccessibleParagraph::getTextAtIndex( sal_Int32 nIndex, sal_Int16 nTextType )
2176 SolarMutexGuard aGuard;
2178 ThrowIfDisposed();
2180 /*accessibility::*/TextSegment aResult;
2181 aResult.SegmentStart = -1;
2182 aResult.SegmentEnd = -1;
2184 const OUString rText = GetString();
2185 // implement the silly specification that first position after
2186 // text must return an empty string, rather than throwing an
2187 // IndexOutOfBoundsException, except for LINE, where the last
2188 // line is returned
2189 if( nIndex == rText.getLength() && AccessibleTextType::LINE != nTextType )
2190 return aResult;
2192 // with error checking
2193 i18n::Boundary aBound;
2194 bool bWord = GetTextBoundary( aBound, rText, nIndex, nTextType );
2196 OSL_ENSURE( aBound.startPos >= 0, "illegal boundary" );
2197 OSL_ENSURE( aBound.startPos <= aBound.endPos, "illegal boundary" );
2199 // return word (if present)
2200 if ( bWord )
2202 aResult.SegmentText = rText.copy( aBound.startPos, aBound.endPos - aBound.startPos );
2203 aResult.SegmentStart = aBound.startPos;
2204 aResult.SegmentEnd = aBound.endPos;
2207 return aResult;
2210 /*accessibility::*/TextSegment SwAccessibleParagraph::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 nTextType )
2212 SolarMutexGuard aGuard;
2214 ThrowIfDisposed();
2216 const OUString rText = GetString();
2218 /*accessibility::*/TextSegment aResult;
2219 aResult.SegmentStart = -1;
2220 aResult.SegmentEnd = -1;
2221 //If nIndex = 0, then nobefore text so return -1 directly.
2222 if( nIndex == 0 )
2223 return aResult;
2224 //Tab will be return when call WORDTYPE
2226 // get starting pos
2227 i18n::Boundary aBound;
2228 if (nIndex == rText.getLength())
2229 aBound.startPos = aBound.endPos = nIndex;
2230 else
2232 bool bTmp = GetTextBoundary( aBound, rText, nIndex, nTextType );
2234 if ( ! bTmp )
2235 aBound.startPos = aBound.endPos = nIndex;
2238 // now skip to previous word
2239 if (nTextType == AccessibleTextType::WORD || nTextType == AccessibleTextType::SENTENCE)
2241 i18n::Boundary preBound = aBound;
2242 while(preBound.startPos==aBound.startPos && nIndex > 0)
2244 nIndex = min(nIndex, preBound.startPos);
2245 if (nIndex <= 0) break;
2246 rText.iterateCodePoints(&nIndex, -1);
2247 GetTextBoundary( preBound, rText, nIndex, nTextType );
2249 //if (nIndex>0)
2250 if (nIndex>=0)
2251 //Tab will be return when call WORDTYPE
2253 aResult.SegmentText = rText.copy( preBound.startPos, preBound.endPos - preBound.startPos );
2254 aResult.SegmentStart = preBound.startPos;
2255 aResult.SegmentEnd = preBound.endPos;
2258 else
2260 bool bWord = false;
2261 while( !bWord )
2263 nIndex = min(nIndex, aBound.startPos);
2264 if (nIndex > 0)
2266 rText.iterateCodePoints(&nIndex, -1);
2267 bWord = GetTextBoundary( aBound, rText, nIndex, nTextType );
2269 else
2270 break; // exit if beginning of string is reached
2273 if (bWord && nIndex<rText.getLength())
2275 aResult.SegmentText = rText.copy( aBound.startPos, aBound.endPos - aBound.startPos );
2276 aResult.SegmentStart = aBound.startPos;
2277 aResult.SegmentEnd = aBound.endPos;
2280 return aResult;
2283 /*accessibility::*/TextSegment SwAccessibleParagraph::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 nTextType )
2285 SolarMutexGuard aGuard;
2287 ThrowIfDisposed();
2289 /*accessibility::*/TextSegment aResult;
2290 aResult.SegmentStart = -1;
2291 aResult.SegmentEnd = -1;
2292 const OUString rText = GetString();
2294 // implement the silly specification that first position after
2295 // text must return an empty string, rather than throwing an
2296 // IndexOutOfBoundsException
2297 if( nIndex == rText.getLength() )
2298 return aResult;
2300 // get first word, then skip to next word
2301 i18n::Boundary aBound;
2302 GetTextBoundary( aBound, rText, nIndex, nTextType );
2303 bool bWord = false;
2304 while( !bWord )
2306 nIndex = max( sal_Int32(nIndex+1), aBound.endPos );
2307 if( nIndex < rText.getLength() )
2308 bWord = GetTextBoundary( aBound, rText, nIndex, nTextType );
2309 else
2310 break; // exit if end of string is reached
2313 if ( bWord )
2315 aResult.SegmentText = rText.copy( aBound.startPos, aBound.endPos - aBound.startPos );
2316 aResult.SegmentStart = aBound.startPos;
2317 aResult.SegmentEnd = aBound.endPos;
2321 sal_Bool bWord = sal_False;
2322 bWord = GetTextBoundary( aBound, rText, nIndex, nTextType );
2324 if (nTextType == AccessibleTextType::WORD)
2326 Boundary nexBound=aBound;
2328 // real current word
2329 if( nIndex <= aBound.endPos && nIndex >= aBound.startPos )
2331 while(nexBound.endPos==aBound.endPos&&nIndex<rText.getLength())
2333 // nIndex = max( (sal_Int32)(nIndex), nexBound.endPos) + 1;
2334 nIndex = max( (sal_Int32)(nIndex), nexBound.endPos) ;
2335 const sal_Unicode* pStr = rText.getStr();
2336 if (pStr)
2338 if( pStr[nIndex] == sal_Unicode(' ') )
2339 nIndex++;
2341 if( nIndex < rText.getLength() )
2343 bWord = GetTextBoundary( nexBound, rText, nIndex, nTextType );
2348 if (bWord && nIndex<rText.getLength())
2350 aResult.SegmentText = rText.copy( nexBound.startPos, nexBound.endPos - nexBound.startPos );
2351 aResult.SegmentStart = nexBound.startPos;
2352 aResult.SegmentEnd = nexBound.endPos;
2356 else
2358 bWord = sal_False;
2359 while( !bWord )
2361 nIndex = max( (sal_Int32)(nIndex+1), aBound.endPos );
2362 if( nIndex < rText.getLength() )
2364 bWord = GetTextBoundary( aBound, rText, nIndex, nTextType );
2366 else
2367 break; // exit if end of string is reached
2369 if (bWord && nIndex<rText.getLength())
2371 aResult.SegmentText = rText.copy( aBound.startPos, aBound.endPos - aBound.startPos );
2372 aResult.SegmentStart = aBound.startPos;
2373 aResult.SegmentEnd = aBound.endPos;
2377 return aResult;
2380 sal_Bool SwAccessibleParagraph::copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
2382 SolarMutexGuard aGuard;
2384 ThrowIfDisposed();
2386 // select and copy (through dispatch mechanism)
2387 setSelection( nStartIndex, nEndIndex );
2388 ExecuteAtViewShell( SID_COPY );
2389 return true;
2392 sal_Bool SwAccessibleParagraph::scrollSubstringTo( sal_Int32 nStartIndex,
2393 sal_Int32 nEndIndex, AccessibleScrollType aScrollType )
2395 SolarMutexGuard aGuard;
2397 ThrowIfDisposed();
2399 // parameter checking
2400 sal_Int32 nLength = GetString().getLength();
2401 if ( ! IsValidRange( nStartIndex, nEndIndex, nLength ) )
2402 throw lang::IndexOutOfBoundsException();
2404 vcl::Window *pWin = GetWindow();
2405 if ( ! pWin )
2406 throw uno::RuntimeException(u"no Window"_ustr, getXWeak());
2408 /* Start and end character bounds, in pixels, relative to the paragraph */
2409 awt::Rectangle startR, endR;
2410 startR = getCharacterBounds(nStartIndex);
2411 endR = getCharacterBounds(nEndIndex);
2413 /* Adjust points to fit the bounding box of both bounds. */
2414 Point sP(std::min(startR.X, endR.X), startR.Y);
2415 Point eP(std::max(startR.X + startR.Width, endR.X + endR.Width), endR.Y + endR.Height);
2417 /* Offset the values relative to the view shell frame */
2418 SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root
2419 Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds ).TopLeft() );
2420 sP += aFramePixPos;
2421 eP += aFramePixPos;
2423 Point startPoint(GetMap()->PixelToCore(sP));
2424 Point endPoint(GetMap()->PixelToCore(eP));
2426 switch (aScrollType)
2428 #ifdef notyet
2429 case AccessibleScrollType_SCROLL_TOP_LEFT:
2430 break;
2431 case AccessibleScrollType_SCROLL_BOTTOM_RIGHT:
2432 break;
2433 case AccessibleScrollType_SCROLL_TOP_EDGE:
2434 break;
2435 case AccessibleScrollType_SCROLL_BOTTOM_EDGE:
2436 break;
2437 case AccessibleScrollType_SCROLL_LEFT_EDGE:
2438 break;
2439 case AccessibleScrollType_SCROLL_RIGHT_EDGE:
2440 break;
2441 #endif
2442 case AccessibleScrollType_SCROLL_ANYWHERE:
2443 break;
2444 default:
2445 return false;
2448 const SwRect aRect(startPoint, endPoint);
2449 SwViewShell* pViewShell = GetMap()->GetShell();
2450 OSL_ENSURE( pViewShell != nullptr, "View shell expected!" );
2452 ScrollMDI(pViewShell, aRect, USHRT_MAX, USHRT_MAX);
2454 return true;
2457 // XAccessibleEditableText
2459 sal_Bool SwAccessibleParagraph::cutText( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
2461 SolarMutexGuard aGuard;
2463 ThrowIfDisposed();
2465 if( !IsEditableState() )
2466 return false;
2468 // select and cut (through dispatch mechanism)
2469 setSelection( nStartIndex, nEndIndex );
2470 ExecuteAtViewShell( SID_CUT );
2471 return true;
2474 sal_Bool SwAccessibleParagraph::pasteText( sal_Int32 nIndex )
2476 SolarMutexGuard aGuard;
2478 ThrowIfDisposed();
2480 if( !IsEditableState() )
2481 return false;
2483 // select and paste (through dispatch mechanism)
2484 setSelection( nIndex, nIndex );
2485 ExecuteAtViewShell( SID_PASTE );
2486 return true;
2489 sal_Bool SwAccessibleParagraph::deleteText( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
2491 return replaceText( nStartIndex, nEndIndex, OUString() );
2494 sal_Bool SwAccessibleParagraph::insertText( const OUString& sText, sal_Int32 nIndex )
2496 return replaceText( nIndex, nIndex, sText );
2499 sal_Bool SwAccessibleParagraph::replaceText(
2500 sal_Int32 nStartIndex, sal_Int32 nEndIndex,
2501 const OUString& sReplacement )
2503 SolarMutexGuard aGuard;
2505 ThrowIfDisposed();
2507 const OUString& rText = GetString();
2509 if( !IsValidRange( nStartIndex, nEndIndex, rText.getLength() ) )
2510 throw lang::IndexOutOfBoundsException();
2512 if( !IsEditableState() )
2513 return false;
2515 // translate positions
2516 TextFrameIndex nStart;
2517 TextFrameIndex nEnd;
2518 bool bSuccess = GetPortionData().GetEditableRange(
2519 nStartIndex, nEndIndex, nStart, nEnd );
2521 // edit only if the range is editable
2522 if( bSuccess )
2524 const SwTextFrame* const pFrame = GetTextFrame();
2525 // create SwPosition for nStartIndex
2526 SwPosition aStartPos(pFrame->MapViewToModelPos(nStart));
2528 // create SwPosition for nEndIndex
2529 SwPosition aEndPos(pFrame->MapViewToModelPos(nEnd));
2531 // now create XTextRange as helper and set string
2532 const rtl::Reference<SwXTextRange> xRange(
2533 SwXTextRange::CreateXTextRange(
2534 const_cast<SwDoc&>(pFrame->GetDoc()), aStartPos, &aEndPos));
2535 xRange->setString(sReplacement);
2537 // delete portion data
2538 ClearPortionData();
2541 return bSuccess;
2545 sal_Bool SwAccessibleParagraph::setAttributes(
2546 sal_Int32 nStartIndex,
2547 sal_Int32 nEndIndex,
2548 const uno::Sequence<PropertyValue>& rAttributeSet )
2550 SolarMutexGuard aGuard;
2552 ThrowIfDisposed();
2554 const OUString& rText = GetString();
2556 if( ! IsValidRange( nStartIndex, nEndIndex, rText.getLength() ) )
2557 throw lang::IndexOutOfBoundsException();
2559 if( !IsEditableState() )
2560 return false;
2562 // create a (dummy) text portion for the sole purpose of calling
2563 // setPropertyValue on it
2564 rtl::Reference<SwXTextPortion> xPortion = CreateUnoPortion( nStartIndex,
2565 nEndIndex );
2567 // build sorted index array
2568 sal_Int32 nLength = rAttributeSet.getLength();
2569 const PropertyValue* pPairs = rAttributeSet.getConstArray();
2570 std::vector<sal_Int32> aIndices(nLength);
2571 std::iota(aIndices.begin(), aIndices.end(), 0);
2572 std::sort(aIndices.begin(), aIndices.end(), IndexCompare(pPairs));
2574 // create sorted sequences according to index array
2575 uno::Sequence< OUString > aNames( nLength );
2576 OUString* pNames = aNames.getArray();
2577 uno::Sequence< uno::Any > aValues( nLength );
2578 uno::Any* pValues = aValues.getArray();
2579 for (sal_Int32 i = 0; i < nLength; ++i)
2581 const PropertyValue& rVal = pPairs[aIndices[i]];
2582 pNames[i] = rVal.Name;
2583 pValues[i] = rVal.Value;
2585 aIndices.clear();
2587 // now set the values
2588 bool bRet = true;
2591 xPortion->setPropertyValues( aNames, aValues );
2593 catch (const UnknownPropertyException&)
2595 // error handling through return code!
2596 bRet = false;
2599 return bRet;
2602 sal_Bool SwAccessibleParagraph::setText( const OUString& sText )
2604 return replaceText(0, GetString().getLength(), sText);
2607 // XAccessibleSelection
2609 void SwAccessibleParagraph::selectAccessibleChild(
2610 sal_Int64 nChildIndex )
2612 ThrowIfDisposed();
2614 m_aSelectionHelper.selectAccessibleChild(nChildIndex);
2617 sal_Bool SwAccessibleParagraph::isAccessibleChildSelected(
2618 sal_Int64 nChildIndex )
2620 ThrowIfDisposed();
2622 return m_aSelectionHelper.isAccessibleChildSelected(nChildIndex);
2625 void SwAccessibleParagraph::clearAccessibleSelection( )
2627 ThrowIfDisposed();
2630 void SwAccessibleParagraph::selectAllAccessibleChildren( )
2632 ThrowIfDisposed();
2634 m_aSelectionHelper.selectAllAccessibleChildren();
2637 sal_Int64 SwAccessibleParagraph::getSelectedAccessibleChildCount( )
2639 ThrowIfDisposed();
2641 return m_aSelectionHelper.getSelectedAccessibleChildCount();
2644 uno::Reference<XAccessible> SwAccessibleParagraph::getSelectedAccessibleChild(
2645 sal_Int64 nSelectedChildIndex )
2647 ThrowIfDisposed();
2649 return m_aSelectionHelper.getSelectedAccessibleChild(nSelectedChildIndex);
2652 // index has to be treated as global child index.
2653 void SwAccessibleParagraph::deselectAccessibleChild(
2654 sal_Int64 nChildIndex )
2656 ThrowIfDisposed();
2658 m_aSelectionHelper.deselectAccessibleChild( nChildIndex );
2661 // XAccessibleHypertext
2663 namespace {
2665 class SwHyperlinkIter_Impl
2667 SwTextFrame const& m_rFrame;
2668 sw::MergedAttrIter m_Iter;
2669 TextFrameIndex m_nStart;
2670 TextFrameIndex m_nEnd;
2672 public:
2673 explicit SwHyperlinkIter_Impl(const SwTextFrame & rTextFrame);
2674 const SwTextAttr *next(SwTextNode const** ppNode = nullptr);
2676 TextFrameIndex startIdx() const { return m_nStart; }
2677 TextFrameIndex endIdx() const { return m_nEnd; }
2682 SwHyperlinkIter_Impl::SwHyperlinkIter_Impl(const SwTextFrame & rTextFrame)
2683 : m_rFrame(rTextFrame)
2684 , m_Iter(rTextFrame)
2685 , m_nStart(rTextFrame.GetOffset())
2687 const SwTextFrame *const pFollFrame = rTextFrame.GetFollow();
2688 m_nEnd = pFollFrame ? pFollFrame->GetOffset() : TextFrameIndex(rTextFrame.GetText().getLength());
2691 const SwTextAttr *SwHyperlinkIter_Impl::next(SwTextNode const** ppNode)
2693 const SwTextAttr *pAttr = nullptr;
2694 if (ppNode)
2696 *ppNode = nullptr;
2699 SwTextNode const* pNode(nullptr);
2700 while (SwTextAttr const*const pHt = m_Iter.NextAttr(&pNode))
2702 if (RES_TXTATR_INETFMT == pHt->Which())
2704 const TextFrameIndex nHtStart(m_rFrame.MapModelToView(pNode, pHt->GetStart()));
2705 const TextFrameIndex nHtEnd(m_rFrame.MapModelToView(pNode, pHt->GetAnyEnd()));
2706 if (nHtEnd > nHtStart &&
2707 ((nHtStart >= m_nStart && nHtStart < m_nEnd) ||
2708 (nHtEnd > m_nStart && nHtEnd <= m_nEnd)))
2710 pAttr = pHt;
2711 if (ppNode)
2713 *ppNode = pNode;
2715 break;
2720 return pAttr;
2723 sal_Int32 SAL_CALL SwAccessibleParagraph::getHyperLinkCount()
2725 SolarMutexGuard aGuard;
2727 ThrowIfDisposed();
2729 sal_Int32 nCount = 0;
2730 // #i77108# - provide hyperlinks also in editable documents.
2732 const SwTextFrame* pTextFrame = GetTextFrame();
2733 SwHyperlinkIter_Impl aIter(*pTextFrame);
2734 while( aIter.next() )
2735 nCount++;
2737 return nCount;
2740 uno::Reference< XAccessibleHyperlink > SAL_CALL
2741 SwAccessibleParagraph::getHyperLink( sal_Int32 nLinkIndex )
2743 SolarMutexGuard aGuard;
2745 ThrowIfDisposed();
2747 const SwTextFrame* pTextFrame = GetTextFrame();
2748 SwHyperlinkIter_Impl aHIter(*pTextFrame);
2749 SwTextNode const* pNode(nullptr);
2750 const SwTextAttr* pHt = aHIter.next(&pNode);
2751 for (sal_Int32 nTIndex = 0; pHt && nTIndex < nLinkIndex; ++nTIndex)
2752 pHt = aHIter.next(&pNode);
2754 if (!pHt)
2755 throw lang::IndexOutOfBoundsException();
2757 rtl::Reference<SwAccessibleHyperlink> xRet;
2758 if (!m_pHyperTextData)
2759 m_pHyperTextData.reset( new SwAccessibleHyperTextData );
2760 SwAccessibleHyperTextData::iterator aIter = m_pHyperTextData->find(pHt);
2761 if (aIter != m_pHyperTextData->end())
2763 xRet = (*aIter).second;
2765 if (!xRet.is())
2767 TextFrameIndex const nHintStart(pTextFrame->MapModelToView(pNode, pHt->GetStart()));
2768 TextFrameIndex const nHintEnd(pTextFrame->MapModelToView(pNode, pHt->GetAnyEnd()));
2769 const sal_Int32 nTmpHStt = GetPortionData().GetAccessiblePosition(
2770 max(aHIter.startIdx(), nHintStart));
2771 const sal_Int32 nTmpHEnd = GetPortionData().GetAccessiblePosition(
2772 min(aHIter.endIdx(), nHintEnd));
2773 xRet = new SwAccessibleHyperlink(*pHt,
2774 *this, nTmpHStt, nTmpHEnd );
2775 if (aIter != m_pHyperTextData->end())
2777 (*aIter).second = xRet.get();
2779 else
2781 m_pHyperTextData->emplace( pHt, xRet );
2784 return xRet;
2787 sal_Int32 SAL_CALL SwAccessibleParagraph::getHyperLinkIndex( sal_Int32 nCharIndex )
2789 SolarMutexGuard aGuard;
2791 ThrowIfDisposed();
2793 // parameter checking
2794 sal_Int32 nLength = GetString().getLength();
2795 if ( ! IsValidPosition( nCharIndex, nLength ) )
2797 throw lang::IndexOutOfBoundsException();
2800 sal_Int32 nRet = -1;
2801 // #i77108#
2803 const SwTextFrame* pTextFrame = GetTextFrame();
2804 SwHyperlinkIter_Impl aHIter(*pTextFrame);
2806 const TextFrameIndex nIdx = GetPortionData().GetCoreViewPosition(nCharIndex);
2807 sal_Int32 nPos = 0;
2808 SwTextNode const* pNode(nullptr);
2809 const SwTextAttr *pHt = aHIter.next(&pNode);
2810 while (pHt && (nIdx < pTextFrame->MapModelToView(pNode, pHt->GetStart())
2811 || nIdx >= pTextFrame->MapModelToView(pNode, pHt->GetAnyEnd())))
2813 pHt = aHIter.next(&pNode);
2814 nPos++;
2817 if( pHt )
2818 nRet = nPos;
2821 if (nRet == -1)
2822 throw lang::IndexOutOfBoundsException();
2823 return nRet;
2826 // #i71360#, #i108125# - adjustments for change tracking text markup
2827 sal_Int32 SAL_CALL SwAccessibleParagraph::getTextMarkupCount( sal_Int32 nTextMarkupType )
2829 SolarMutexGuard g;
2831 std::unique_ptr<SwTextMarkupHelper> pTextMarkupHelper;
2832 switch ( nTextMarkupType )
2834 case text::TextMarkupType::TRACK_CHANGE_INSERTION:
2835 case text::TextMarkupType::TRACK_CHANGE_DELETION:
2836 case text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE:
2838 pTextMarkupHelper.reset( new SwTextMarkupHelper(
2839 GetPortionData(),
2840 *(mpParaChangeTrackInfo->getChangeTrackingTextMarkupList( nTextMarkupType ) )) );
2842 break;
2843 default:
2845 const SwTextFrame* const pFrame = GetTextFrame();
2846 pTextMarkupHelper.reset(new SwTextMarkupHelper(GetPortionData(), *pFrame));
2850 return pTextMarkupHelper->getTextMarkupCount( nTextMarkupType );
2853 //MSAA Extension Implementation in app module
2854 sal_Bool SAL_CALL SwAccessibleParagraph::scrollToPosition( const css::awt::Point&, sal_Bool )
2856 return false;
2859 sal_Int32 SAL_CALL SwAccessibleParagraph::getSelectedPortionCount( )
2861 SolarMutexGuard g;
2863 sal_Int32 nSelected = 0;
2864 SwPaM* pCursor = GetCursor( true );
2865 if( pCursor != nullptr )
2867 // get SwPosition for my node
2868 const SwTextFrame* const pFrame = GetTextFrame();
2869 SwNodeOffset nFirstNode(pFrame->GetTextNodeFirst()->GetIndex());
2870 SwNodeOffset nLastNode;
2871 if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara())
2873 nLastNode = pMerged->pLastNode->GetIndex();
2875 else
2877 nLastNode = nFirstNode;
2880 // iterate over ring
2881 for(SwPaM& rTmpCursor : pCursor->GetRingContainer())
2883 // ignore, if no mark
2884 if( rTmpCursor.HasMark() )
2886 // check whether frame's node(s) are 'inside' pCursor
2887 SwPosition* pStart = rTmpCursor.Start();
2888 SwNodeOffset nStartIndex = pStart->GetNodeIndex();
2889 SwPosition* pEnd = rTmpCursor.End();
2890 SwNodeOffset nEndIndex = pEnd->GetNodeIndex();
2891 if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex))
2893 nSelected++;
2895 // else: this PaM doesn't point to this paragraph
2897 // else: this PaM is collapsed and doesn't select anything
2900 return nSelected;
2904 sal_Int32 SAL_CALL SwAccessibleParagraph::getSeletedPositionStart( sal_Int32 nSelectedPortionIndex )
2906 SolarMutexGuard aGuard;
2908 ThrowIfDisposed();
2910 sal_Int32 nStart=-1, nEnd=-1;
2911 /*sal_Bool bSelected = */GetSelectionAtIndex(&nSelectedPortionIndex, nStart, nEnd );
2912 return nStart;
2915 sal_Int32 SAL_CALL SwAccessibleParagraph::getSeletedPositionEnd( sal_Int32 nSelectedPortionIndex )
2917 SolarMutexGuard aGuard;
2919 ThrowIfDisposed();
2921 sal_Int32 nStart=-1, nEnd=-1;
2922 /*sal_Bool bSelected = */GetSelectionAtIndex(&nSelectedPortionIndex, nStart, nEnd );
2923 return nEnd;
2926 sal_Bool SAL_CALL SwAccessibleParagraph::removeSelection( sal_Int32 selectionIndex )
2928 SolarMutexGuard g;
2930 if(selectionIndex < 0) return false;
2932 sal_Int32 nSelected = selectionIndex;
2934 // get the selection, and test whether it affects our text node
2935 SwPaM* pCursor = GetCursor( true );
2937 if( pCursor != nullptr )
2939 bool bRet = false;
2941 // get SwPosition for my node
2942 const SwTextFrame* const pFrame = GetTextFrame();
2943 SwNodeOffset nFirstNode(pFrame->GetTextNodeFirst()->GetIndex());
2944 SwNodeOffset nLastNode;
2945 if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara())
2947 nLastNode = pMerged->pLastNode->GetIndex();
2949 else
2951 nLastNode = nFirstNode;
2954 // iterate over ring
2955 SwPaM* pRingStart = pCursor;
2958 // ignore, if no mark
2959 if( pCursor->HasMark() )
2961 // check whether frame's node(s) are 'inside' pCursor
2962 SwPosition* pStart = pCursor->Start();
2963 SwNodeOffset nStartIndex = pStart->GetNodeIndex();
2964 SwPosition* pEnd = pCursor->End();
2965 SwNodeOffset nEndIndex = pEnd->GetNodeIndex();
2966 if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex))
2968 if( nSelected == 0 )
2970 pCursor->MoveTo(nullptr);
2971 delete pCursor;
2972 bRet = true;
2974 else
2976 nSelected--;
2980 // else: this PaM is collapsed and doesn't select anything
2981 if(!bRet)
2982 pCursor = pCursor->GetNext();
2984 while( !bRet && (pCursor != pRingStart) );
2986 return true;
2989 sal_Int32 SAL_CALL SwAccessibleParagraph::addSelection( sal_Int32, sal_Int32 startOffset, sal_Int32 endOffset)
2991 SolarMutexGuard aGuard;
2993 ThrowIfDisposed();
2995 // parameter checking
2996 sal_Int32 nLength = GetString().getLength();
2997 if ( ! IsValidRange( startOffset, endOffset, nLength ) )
2999 throw lang::IndexOutOfBoundsException();
3002 sal_Int32 nSelectedCount = getSelectedPortionCount();
3003 for ( sal_Int32 i = nSelectedCount ; i >= 0 ; i--)
3005 sal_Int32 nStart, nEnd;
3006 bool bSelected = GetSelectionAtIndex(&i, nStart, nEnd );
3007 if(bSelected)
3009 if(nStart <= nEnd )
3011 if (( startOffset>=nStart && startOffset <=nEnd ) || //startOffset in a selection
3012 ( endOffset>=nStart && endOffset <=nEnd ) || //endOffset in a selection
3013 ( startOffset <= nStart && endOffset >=nEnd) || //start and end include the old selection
3014 ( startOffset >= nStart && endOffset <=nEnd) )
3016 removeSelection(i);
3020 else
3022 if (( startOffset>=nEnd && startOffset <=nStart ) || //startOffset in a selection
3023 ( endOffset>=nEnd && endOffset <=nStart ) || //endOffset in a selection
3024 ( startOffset <= nStart && endOffset >=nEnd) || //start and end include the old selection
3025 ( startOffset >= nStart && endOffset <=nEnd) )
3028 removeSelection(i);
3035 // get cursor shell
3036 SwCursorShell* pCursorShell = GetCursorShell();
3037 if( pCursorShell != nullptr )
3039 // create pam for selection
3040 pCursorShell->StartAction();
3041 const SwTextFrame* const pFrame = GetTextFrame();
3042 SwPaM* aPaM = pCursorShell->CreateCursor();
3043 aPaM->SetMark();
3044 *aPaM->GetPoint() = pFrame->MapViewToModelPos(GetPortionData().GetCoreViewPosition(startOffset));
3045 *aPaM->GetMark() = pFrame->MapViewToModelPos(GetPortionData().GetCoreViewPosition(endOffset));
3046 pCursorShell->EndAction();
3049 return 0;
3052 /*accessibility::*/TextSegment SAL_CALL
3053 SwAccessibleParagraph::getTextMarkup( sal_Int32 nTextMarkupIndex,
3054 sal_Int32 nTextMarkupType )
3056 SolarMutexGuard g;
3058 std::unique_ptr<SwTextMarkupHelper> pTextMarkupHelper;
3059 switch ( nTextMarkupType )
3061 case text::TextMarkupType::TRACK_CHANGE_INSERTION:
3062 case text::TextMarkupType::TRACK_CHANGE_DELETION:
3063 case text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE:
3065 pTextMarkupHelper.reset( new SwTextMarkupHelper(
3066 GetPortionData(),
3067 *(mpParaChangeTrackInfo->getChangeTrackingTextMarkupList( nTextMarkupType ) )) );
3069 break;
3070 default:
3072 const SwTextFrame* const pFrame = GetTextFrame();
3073 pTextMarkupHelper.reset(new SwTextMarkupHelper(GetPortionData(), *pFrame));
3077 return pTextMarkupHelper->getTextMarkup( nTextMarkupIndex, nTextMarkupType );
3080 uno::Sequence< /*accessibility::*/TextSegment > SAL_CALL
3081 SwAccessibleParagraph::getTextMarkupAtIndex( sal_Int32 nCharIndex,
3082 sal_Int32 nTextMarkupType )
3084 SolarMutexGuard g;
3086 // parameter checking
3087 const sal_Int32 nLength = GetString().getLength();
3088 if ( ! IsValidPosition( nCharIndex, nLength ) )
3090 throw lang::IndexOutOfBoundsException();
3093 std::unique_ptr<SwTextMarkupHelper> pTextMarkupHelper;
3094 switch ( nTextMarkupType )
3096 case text::TextMarkupType::TRACK_CHANGE_INSERTION:
3097 case text::TextMarkupType::TRACK_CHANGE_DELETION:
3098 case text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE:
3100 pTextMarkupHelper.reset( new SwTextMarkupHelper(
3101 GetPortionData(),
3102 *(mpParaChangeTrackInfo->getChangeTrackingTextMarkupList( nTextMarkupType ) )) );
3104 break;
3105 default:
3107 const SwTextFrame* const pFrame = GetTextFrame();
3108 pTextMarkupHelper.reset(new SwTextMarkupHelper(GetPortionData(), *pFrame));
3112 return pTextMarkupHelper->getTextMarkupAtIndex( nCharIndex, nTextMarkupType );
3115 // #i89175#
3116 sal_Int32 SAL_CALL SwAccessibleParagraph::getLineNumberAtIndex( sal_Int32 nIndex )
3118 SolarMutexGuard g;
3120 // parameter checking
3121 const sal_Int32 nLength = GetString().getLength();
3122 if ( ! IsValidPosition( nIndex, nLength ) )
3124 throw lang::IndexOutOfBoundsException();
3127 const sal_Int32 nLineNo = GetPortionData().GetLineNo( nIndex );
3128 return nLineNo;
3131 /*accessibility::*/TextSegment SAL_CALL
3132 SwAccessibleParagraph::getTextAtLineNumber( sal_Int32 nLineNo )
3134 SolarMutexGuard g;
3136 // parameter checking
3137 if ( nLineNo < 0 ||
3138 nLineNo >= GetPortionData().GetLineCount() )
3140 throw lang::IndexOutOfBoundsException();
3143 i18n::Boundary aLineBound;
3144 GetPortionData().GetBoundaryOfLine( nLineNo, aLineBound );
3146 /*accessibility::*/TextSegment aTextAtLine;
3147 const OUString rText = GetString();
3148 aTextAtLine.SegmentText = rText.copy( aLineBound.startPos,
3149 aLineBound.endPos - aLineBound.startPos );
3150 aTextAtLine.SegmentStart = aLineBound.startPos;
3151 aTextAtLine.SegmentEnd = aLineBound.endPos;
3153 return aTextAtLine;
3156 /*accessibility::*/TextSegment SAL_CALL SwAccessibleParagraph::getTextAtLineWithCaret()
3158 SolarMutexGuard g;
3160 const sal_Int32 nLineNoOfCaret = getNumberOfLineWithCaret();
3162 if ( nLineNoOfCaret >= 0 &&
3163 nLineNoOfCaret < GetPortionData().GetLineCount() )
3165 return getTextAtLineNumber( nLineNoOfCaret );
3168 return /*accessibility::*/TextSegment();
3171 sal_Int32 SAL_CALL SwAccessibleParagraph::getNumberOfLineWithCaret()
3173 SolarMutexGuard g;
3175 const sal_Int32 nCaretPos = getCaretPosition();
3176 const sal_Int32 nLength = GetString().getLength();
3177 if ( !IsValidPosition( nCaretPos, nLength ) )
3179 return -1;
3182 sal_Int32 nLineNo = GetPortionData().GetLineNo( nCaretPos );
3184 // special handling for cursor positioned at end of text line via End key
3185 if ( nCaretPos != 0 )
3187 i18n::Boundary aLineBound;
3188 GetPortionData().GetBoundaryOfLine( nLineNo, aLineBound );
3189 if ( nCaretPos == aLineBound.startPos )
3191 SwCursorShell* pCursorShell = SwAccessibleParagraph::GetCursorShell();
3192 if ( pCursorShell != nullptr )
3194 const awt::Rectangle aCharRect = getCharacterBounds( nCaretPos );
3196 const SwRect& aCursorCoreRect = pCursorShell->GetCharRect();
3197 // translate core coordinates into accessibility coordinates
3198 vcl::Window *pWin = GetWindow();
3199 if (!pWin)
3201 throw uno::RuntimeException(u"no Window"_ustr, getXWeak());
3204 tools::Rectangle aScreenRect( GetMap()->CoreToPixel( aCursorCoreRect ));
3206 SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root
3207 Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds ).TopLeft() );
3208 aScreenRect.Move( -aFramePixPos.getX(), -aFramePixPos.getY() );
3210 // convert into AWT Rectangle
3211 const awt::Rectangle aCursorRect( aScreenRect.Left(),
3212 aScreenRect.Top(),
3213 aScreenRect.GetWidth(),
3214 aScreenRect.GetHeight() );
3216 if ( aCharRect.X != aCursorRect.X ||
3217 aCharRect.Y != aCursorRect.Y )
3219 --nLineNo;
3225 return nLineNo;
3228 // #i108125#
3229 void SwAccessibleParagraph::Notify(SfxBroadcaster&, const SfxHint&)
3231 mpParaChangeTrackInfo->reset();
3234 bool SwAccessibleParagraph::GetSelectionAtIndex(
3235 sal_Int32 * pSelection, sal_Int32& nStart, sal_Int32& nEnd)
3237 if (pSelection && *pSelection < 0) return false;
3239 bool bRet = false;
3240 nStart = -1;
3241 nEnd = -1;
3243 // get the selection, and test whether it affects our text node
3244 SwPaM* pCursor = GetCursor( true );
3245 if( pCursor != nullptr )
3247 // get SwPosition for my node
3248 const SwTextFrame* const pFrame = GetTextFrame();
3249 SwNodeOffset nFirstNode(pFrame->GetTextNodeFirst()->GetIndex());
3250 SwNodeOffset nLastNode;
3251 if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara())
3253 nLastNode = pMerged->pLastNode->GetIndex();
3255 else
3257 nLastNode = nFirstNode;
3260 // iterate over ring
3261 for(SwPaM& rTmpCursor : pCursor->GetRingContainer())
3263 // ignore, if no mark
3264 if( rTmpCursor.HasMark() )
3266 // check whether frame's node(s) are 'inside' pCursor
3267 SwPosition* pStart = rTmpCursor.Start();
3268 SwNodeOffset nStartIndex = pStart->GetNodeIndex();
3269 SwPosition* pEnd = rTmpCursor.End();
3270 SwNodeOffset nEndIndex = pEnd->GetNodeIndex();
3271 if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex))
3273 if (!pSelection || *pSelection == 0)
3275 // translate start and end positions
3277 // start position
3278 sal_Int32 nLocalStart = -1;
3279 if (nStartIndex < nFirstNode)
3281 // selection starts in previous node:
3282 // then our local selection starts with the paragraph
3283 nLocalStart = 0;
3285 else
3287 assert(FrameContainsNode(*pFrame, nStartIndex));
3289 // selection starts in this node:
3290 // then check whether it's before or inside our part of
3291 // the paragraph, and if so, get the proper position
3292 const TextFrameIndex nCoreStart =
3293 pFrame->MapModelToViewPos(*pStart);
3294 if( nCoreStart <
3295 GetPortionData().GetFirstValidCorePosition() )
3297 nLocalStart = 0;
3299 else if( nCoreStart <=
3300 GetPortionData().GetLastValidCorePosition() )
3302 SAL_WARN_IF(
3303 !GetPortionData().IsValidCorePosition(
3304 nCoreStart),
3305 "sw.a11y",
3306 "problem determining valid core position");
3308 nLocalStart =
3309 GetPortionData().GetAccessiblePosition(
3310 nCoreStart );
3314 // end position
3315 sal_Int32 nLocalEnd = -1;
3316 if (nLastNode < nEndIndex)
3318 // selection ends in following node:
3319 // then our local selection extends to the end
3320 nLocalEnd = GetPortionData().GetAccessibleString().
3321 getLength();
3323 else
3325 assert(FrameContainsNode(*pFrame, nEndIndex));
3327 // selection ends in this node: then select everything
3328 // before our part of the node
3329 const TextFrameIndex nCoreEnd =
3330 pFrame->MapModelToViewPos(*pEnd);
3331 if( nCoreEnd >
3332 GetPortionData().GetLastValidCorePosition() )
3334 // selection extends beyond out part of this para
3335 nLocalEnd = GetPortionData().GetAccessibleString().
3336 getLength();
3338 else if( nCoreEnd >=
3339 GetPortionData().GetFirstValidCorePosition() )
3341 // selection is inside our part of this para
3342 SAL_WARN_IF(
3343 !GetPortionData().IsValidCorePosition(
3344 nCoreEnd),
3345 "sw.a11y",
3346 "problem determining valid core position");
3348 nLocalEnd = GetPortionData().GetAccessiblePosition(
3349 nCoreEnd );
3353 if( ( nLocalStart != -1 ) && ( nLocalEnd != -1 ) )
3355 nStart = nLocalStart;
3356 nEnd = nLocalEnd;
3357 bRet = true;
3359 } // if hit the index
3360 else
3362 --*pSelection;
3365 // else: this PaM doesn't point to this paragraph
3367 // else: this PaM is collapsed and doesn't select anything
3368 if(bRet)
3369 break;
3372 // else: nocursor -> no selection
3374 if (pSelection && bRet)
3376 sal_Int32 nCaretPos = GetCaretPos();
3377 if( nStart == nCaretPos )
3378 std::swap( nStart, nEnd );
3380 return bRet;
3383 sal_Int16 SAL_CALL SwAccessibleParagraph::getAccessibleRole()
3385 std::scoped_lock aGuard( m_Mutex );
3387 //Get the real heading level, Heading1 ~ Heading10
3388 if (m_nHeadingLevel > 0)
3389 return AccessibleRole::HEADING;
3390 if (m_bIsBlockQuote)
3391 return AccessibleRole::BLOCK_QUOTE;
3392 else
3393 return AccessibleRole::PARAGRAPH;
3396 //Get the real heading level, Heading1 ~ Heading10
3397 sal_Int32 SwAccessibleParagraph::GetRealHeadingLevel()
3399 uno::Reference< css::beans::XPropertySet > xPortion = CreateUnoPortion( 0, 0 );
3400 uno::Any styleAny = xPortion->getPropertyValue( u"ParaStyleName"_ustr );
3401 OUString sValue;
3402 if (styleAny >>= sValue)
3404 sal_Int32 length = sValue.getLength();
3405 if (length == 9 || length == 10)
3407 if (sValue.startsWith("Heading"))
3409 std::u16string_view intStr = sValue.subView(8);
3410 sal_Int32 headingLevel = o3tl::toInt32(intStr);
3411 return headingLevel;
3415 return -1;
3418 bool SwAccessibleParagraph::IsBlockQuote()
3420 uno::Reference<css::beans::XPropertySet> xPortion = CreateUnoPortion(0, 0);
3421 uno::Any aStyleAny = xPortion->getPropertyValue(u"ParaStyleName"_ustr);
3422 OUString sValue;
3423 if (aStyleAny >>= sValue)
3424 return sValue == "Quotations";
3425 return false;
3428 uno::Any SAL_CALL SwAccessibleParagraph::getExtendedAttributes()
3430 SolarMutexGuard g;
3432 OUString strHeading;
3433 if (m_nHeadingLevel >= 0)
3435 // report heading level using the "level" object attribute as specified in ARIA,
3436 // maps to attributes of the same name for AT-SPI, IAccessible2, UIA
3437 // https://www.w3.org/TR/core-aam-1.2/#ariaLevelHeading
3438 strHeading = "level:" + OUString::number(m_nHeadingLevel) + ";";
3441 return uno::Any(strHeading);
3444 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */