Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / svx / source / accessibility / AccessibleTextHelper.cxx
blobd836f2ee49707655ae463fd9940ffd8e2784f99a
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 <sal/config.h>
22 #include <cstdlib>
23 #include <memory>
24 #include <mutex>
25 #include <utility>
26 #include <algorithm>
27 #include <sal/log.hxx>
28 #include <com/sun/star/uno/Any.hxx>
29 #include <com/sun/star/uno/Reference.hxx>
30 #include <com/sun/star/awt/Point.hpp>
31 #include <com/sun/star/awt/Rectangle.hpp>
32 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
33 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
34 #include <com/sun/star/accessibility/XAccessible.hpp>
35 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
36 #include <com/sun/star/accessibility/XAccessibleComponent.hpp>
37 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
38 #include <comphelper/accessibleeventnotifier.hxx>
39 #include <vcl/svapp.hxx>
40 #include <vcl/textdata.hxx>
41 #include <vcl/unohelp.hxx>
44 // Project-local header
47 #include "AccessibleTextEventQueue.hxx"
48 #include <svx/AccessibleTextHelper.hxx>
50 #include <editeng/unoedhlp.hxx>
51 #include <editeng/unoedprx.hxx>
52 #include <editeng/AccessibleParaManager.hxx>
53 #include <editeng/AccessibleEditableTextPara.hxx>
54 #include <svx/svdmodel.hxx>
55 #include <svx/svdpntv.hxx>
56 #include <cell.hxx>
57 #include "../table/accessiblecell.hxx"
58 #include <editeng/editdata.hxx>
59 #include <tools/debug.hxx>
60 #include <comphelper/diagnose_ex.hxx>
62 using namespace ::com::sun::star;
63 using namespace ::com::sun::star::accessibility;
65 namespace accessibility
68 // AccessibleTextHelper_Impl declaration
70 template < typename first_type, typename second_type >
71 static ::std::pair< first_type, second_type > makeSortedPair( first_type first,
72 second_type second )
74 if( first > second )
75 return ::std::make_pair( second, first );
76 else
77 return ::std::make_pair( first, second );
80 class AccessibleTextHelper_Impl : public SfxListener
82 public:
83 typedef ::std::vector< sal_Int16 > VectorOfStates;
85 // receive pointer to our frontend class and view window
86 AccessibleTextHelper_Impl();
87 virtual ~AccessibleTextHelper_Impl() override;
89 // XAccessibleContext child handling methods
90 sal_Int64 getAccessibleChildCount() const;
91 uno::Reference< XAccessible > getAccessibleChild( sal_Int64 i );
93 // XAccessibleEventBroadcaster child related methods
94 void addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener );
95 void removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener );
97 // XAccessibleComponent child related methods
98 uno::Reference< XAccessible > getAccessibleAtPoint( const awt::Point& aPoint );
100 SvxEditSourceAdapter& GetEditSource() const;
102 void SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource );
104 void SetEventSource( const uno::Reference< XAccessible >& rInterface )
106 mxFrontEnd = rInterface;
109 void SetOffset( const Point& );
110 Point GetOffset() const
112 std::scoped_lock aGuard( maMutex ); Point aPoint( maOffset );
113 return aPoint;
116 void SetStartIndex( sal_Int32 nOffset );
117 sal_Int32 GetStartIndex() const
119 // Strictly correct only with locked solar mutex, // but
120 // here we rely on the fact that sal_Int32 access is
121 // atomic
122 return mnStartIndex;
125 void SetAdditionalChildStates( sal_Int64 nChildStates );
127 void Dispose();
129 // do NOT hold object mutex when calling this! Danger of deadlock
130 void FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue = uno::Any(), const uno::Any& rOldValue = uno::Any() ) const;
131 void FireEvent( const AccessibleEventObject& rEvent ) const;
133 void SetFocus( bool bHaveFocus );
134 bool HaveFocus() const
136 // No locking of solar mutex here, since we rely on the fact
137 // that sal_Bool access is atomic
138 return mbThisHasFocus;
140 void SetChildFocus( sal_Int32 nChild, bool bHaveFocus );
141 void SetShapeFocus( bool bHaveFocus );
142 void ChangeChildFocus( sal_Int32 nNewChild );
144 #ifdef DBG_UTIL
145 void CheckInvariants() const;
146 #endif
148 // checks all children for visibility, throws away invisible ones
149 void UpdateVisibleChildren( bool bBroadcastEvents=true );
151 // check all children for changes in position and size
152 void UpdateBoundRect();
154 // calls SetSelection on the forwarder and updates maLastSelection
155 // cache.
156 void UpdateSelection();
158 private:
160 // Process event queue
161 void ProcessQueue();
163 // syntactic sugar for FireEvent
164 void GotPropertyEvent( const uno::Any& rNewValue, const sal_Int16 nEventId ) const { FireEvent( nEventId, rNewValue ); }
166 // shutdown usage of current edit source on myself and the children.
167 void ShutdownEditSource();
169 void ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast );
171 virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
173 comphelper::AccessibleEventNotifier::TClientId getNotifierClientId() const { return mnNotifierClientId; }
175 // lock solar mutex before
176 SvxTextForwarder& GetTextForwarder() const;
177 // lock solar mutex before
178 SvxViewForwarder& GetViewForwarder() const;
179 // lock solar mutex before
180 SvxEditViewForwarder& GetEditViewForwarder() const;
182 // are we in edit mode?
183 bool IsActive() const;
185 // our frontend class (the one implementing the actual
186 // interface). That's not necessarily the one containing the impl
187 // pointer!
188 uno::Reference< XAccessible > mxFrontEnd;
190 // a wrapper for the text forwarders (guarded by solar mutex)
191 mutable SvxEditSourceAdapter maEditSource;
193 // store last selection (to correctly report selection changes, guarded by solar mutex)
194 ESelection maLastSelection;
196 // cache range of visible children (guarded by solar mutex)
197 sal_Int32 mnFirstVisibleChild;
198 sal_Int32 mnLastVisibleChild;
200 // offset to add to all our children (unguarded, relying on
201 // the fact that sal_Int32 access is atomic)
202 sal_Int32 mnStartIndex;
204 // the object handling our children (guarded by solar mutex)
205 ::accessibility::AccessibleParaManager maParaManager;
207 // Queued events from Notify() (guarded by solar mutex)
208 AccessibleTextEventQueue maEventQueue;
210 // spin lock to prevent notify in notify (guarded by solar mutex)
211 bool mbInNotify;
213 // whether the object or its children has the focus set (guarded by solar mutex)
214 bool mbGroupHasFocus;
216 // whether we (this object) has the focus set (guarded by solar mutex)
217 bool mbThisHasFocus;
219 mutable std::mutex maMutex;
221 /// our current offset to the containing shape/cell (guarded by maMutex)
222 Point maOffset;
224 /// client Id from AccessibleEventNotifier
225 comphelper::AccessibleEventNotifier::TClientId mnNotifierClientId;
226 static constexpr comphelper::AccessibleEventNotifier::TClientId snNotifierClientRevoked
227 = std::numeric_limits<comphelper::AccessibleEventNotifier::TClientId>::max();
230 AccessibleTextHelper_Impl::AccessibleTextHelper_Impl() :
231 maLastSelection( EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND,EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND ),
232 mnFirstVisibleChild( -1 ),
233 mnLastVisibleChild( -2 ),
234 mnStartIndex( 0 ),
235 mbInNotify( false ),
236 mbGroupHasFocus( false ),
237 mbThisHasFocus( false ),
238 maOffset(0,0),
239 // well, that's strictly exception safe, though not really
240 // robust. We rely on the fact that this member is constructed
241 // last, and that the constructor body is empty, thus no
242 // chance for exceptions once the Id is fetched. Nevertheless,
243 // normally should employ RAII here...
244 mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient())
246 SAL_INFO("svx", "received ID: " << mnNotifierClientId );
249 AccessibleTextHelper_Impl::~AccessibleTextHelper_Impl()
251 SolarMutexGuard aGuard;
255 // call Dispose here, too, since we've some resources not
256 // automatically freed otherwise
257 Dispose();
259 catch( const uno::Exception& ) {}
262 SvxTextForwarder& AccessibleTextHelper_Impl::GetTextForwarder() const
264 if( !maEditSource.IsValid() )
265 throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
267 SvxTextForwarder* pTextForwarder = maEditSource.GetTextForwarder();
269 if( !pTextForwarder )
270 throw uno::RuntimeException("Unable to fetch text forwarder, model might be dead", mxFrontEnd);
272 if( !pTextForwarder->IsValid() )
273 throw uno::RuntimeException("Text forwarder is invalid, model might be dead", mxFrontEnd);
275 return *pTextForwarder;
278 SvxViewForwarder& AccessibleTextHelper_Impl::GetViewForwarder() const
280 if( !maEditSource.IsValid() )
281 throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
283 SvxViewForwarder* pViewForwarder = maEditSource.GetViewForwarder();
285 if( !pViewForwarder )
286 throw uno::RuntimeException("Unable to fetch view forwarder, model might be dead", mxFrontEnd);
288 if( !pViewForwarder->IsValid() )
289 throw uno::RuntimeException("View forwarder is invalid, model might be dead", mxFrontEnd);
291 return *pViewForwarder;
294 SvxEditViewForwarder& AccessibleTextHelper_Impl::GetEditViewForwarder() const
296 if( !maEditSource.IsValid() )
297 throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
299 SvxEditViewForwarder* pViewForwarder = maEditSource.GetEditViewForwarder();
301 if( !pViewForwarder )
303 throw uno::RuntimeException("No edit view forwarder, object not in edit mode", mxFrontEnd);
306 if( !pViewForwarder->IsValid() )
308 throw uno::RuntimeException("View forwarder is invalid, object not in edit mode", mxFrontEnd);
311 return *pViewForwarder;
314 SvxEditSourceAdapter& AccessibleTextHelper_Impl::GetEditSource() const
316 if( !maEditSource.IsValid() )
317 throw uno::RuntimeException("AccessibleTextHelper_Impl::GetEditSource: no edit source", mxFrontEnd );
318 return maEditSource;
321 namespace {
323 // functor for sending child events (no stand-alone function, they are maybe not inlined)
324 class AccessibleTextHelper_OffsetChildIndex
326 public:
327 explicit AccessibleTextHelper_OffsetChildIndex( sal_Int32 nDifference ) : mnDifference(nDifference) {}
328 void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
330 rPara.SetIndexInParent( rPara.GetIndexInParent() + mnDifference );
333 private:
334 const sal_Int32 mnDifference;
339 void AccessibleTextHelper_Impl::SetStartIndex( sal_Int32 nOffset )
341 sal_Int32 nOldOffset( mnStartIndex );
343 mnStartIndex = nOffset;
345 if( nOldOffset != nOffset )
347 // update children
348 AccessibleTextHelper_OffsetChildIndex aFunctor( nOffset - nOldOffset );
350 ::std::for_each( maParaManager.begin(), maParaManager.end(),
351 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_OffsetChildIndex > (aFunctor) );
355 void AccessibleTextHelper_Impl::SetAdditionalChildStates( sal_Int64 nChildStates )
357 maParaManager.SetAdditionalChildStates( nChildStates );
360 void AccessibleTextHelper_Impl::SetChildFocus( sal_Int32 nChild, bool bHaveFocus )
362 if( bHaveFocus )
364 if( mbThisHasFocus )
365 SetShapeFocus( false );
367 maParaManager.SetFocus( nChild );
369 // we just received the focus, also send caret event then
370 UpdateSelection();
372 SAL_INFO("svx", "Paragraph " << nChild << " received focus");
374 else
376 maParaManager.SetFocus( -1 );
378 SAL_INFO("svx", "Paragraph " << nChild << " lost focus");
380 if( mbGroupHasFocus )
381 SetShapeFocus( true );
385 void AccessibleTextHelper_Impl::ChangeChildFocus( sal_Int32 nNewChild )
387 if( mbThisHasFocus )
388 SetShapeFocus( false );
390 mbGroupHasFocus = true;
391 maParaManager.SetFocus( nNewChild );
393 SAL_INFO("svx", "Paragraph " << nNewChild << " received focus");
396 void AccessibleTextHelper_Impl::SetShapeFocus( bool bHaveFocus )
398 bool bOldFocus( mbThisHasFocus );
400 mbThisHasFocus = bHaveFocus;
402 if( bOldFocus == bHaveFocus )
403 return;
405 if( bHaveFocus )
407 if( mxFrontEnd.is() )
409 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
410 if ( !pAccessibleCell )
411 GotPropertyEvent( uno::Any(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED );
412 else // the focus event on cell should be fired on table directly
414 AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable();
415 if (pAccTable)
416 pAccTable->SetStateDirectly(AccessibleStateType::FOCUSED);
419 SAL_INFO("svx", "Parent object received focus" );
421 else
423 // The focus state should be reset directly on table.
424 //LostPropertyEvent( uno::makeAny(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED );
425 if( mxFrontEnd.is() )
427 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
428 if ( !pAccessibleCell )
429 FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::Any(AccessibleStateType::FOCUSED) );
430 else
432 AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable();
433 if (pAccTable)
434 pAccTable->ResetStateDirectly(AccessibleStateType::FOCUSED);
437 SAL_INFO("svx", "Parent object lost focus" );
441 void AccessibleTextHelper_Impl::SetFocus( bool bHaveFocus )
443 bool bOldFocus( mbGroupHasFocus );
445 mbGroupHasFocus = bHaveFocus;
447 if( IsActive() )
451 // find the one with the cursor and get/set focus accordingly
452 ESelection aSelection;
453 if( GetEditViewForwarder().GetSelection( aSelection ) )
454 SetChildFocus( aSelection.nEndPara, bHaveFocus );
456 catch( const uno::Exception& ) {}
458 else if( bOldFocus != bHaveFocus )
460 SetShapeFocus( bHaveFocus );
463 SAL_INFO("svx", "focus changed, Object " << this << ", state: " << (bHaveFocus ? "focused" : "not focused") );
466 bool AccessibleTextHelper_Impl::IsActive() const
470 SvxEditSource& rEditSource = GetEditSource();
471 SvxEditViewForwarder* pViewForwarder = rEditSource.GetEditViewForwarder();
473 if( !pViewForwarder )
474 return false;
476 if( mxFrontEnd.is() )
478 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
479 if ( pAccessibleCell )
481 sdr::table::CellRef xCell = pAccessibleCell->getCellRef();
482 if ( xCell.is() )
483 return xCell->IsActiveCell();
486 return pViewForwarder->IsValid();
488 catch( const uno::RuntimeException& )
490 return false;
494 void AccessibleTextHelper_Impl::UpdateSelection()
498 ESelection aSelection;
499 if( GetEditViewForwarder().GetSelection( aSelection ) )
501 if( maLastSelection != aSelection &&
502 aSelection.nEndPara < maParaManager.GetNum() )
504 // #103998# Not that important, changed from assertion to trace
505 if( mbThisHasFocus )
507 SAL_INFO("svx", "Parent has focus!");
510 sal_Int32 nMaxValidParaIndex( GetTextForwarder().GetParagraphCount() - 1 );
512 // notify all affected paragraphs (TODO: may be suboptimal,
513 // since some paragraphs might stay selected)
514 if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND )
516 // Did the caret move from one paragraph to another?
517 // #100530# no caret events if not focused.
518 if( mbGroupHasFocus &&
519 maLastSelection.nEndPara != aSelection.nEndPara )
521 if( maLastSelection.nEndPara < maParaManager.GetNum() )
523 maParaManager.FireEvent( ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ),
524 ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex )+1,
525 AccessibleEventId::CARET_CHANGED,
526 uno::Any(static_cast<sal_Int32>(-1)),
527 uno::Any(maLastSelection.nEndPos) );
530 ChangeChildFocus( aSelection.nEndPara );
532 SAL_INFO(
533 "svx",
534 "focus changed, Object: " << this
535 << ", Paragraph: " << aSelection.nEndPara
536 << ", Last paragraph: "
537 << maLastSelection.nEndPara);
541 // #100530# no caret events if not focused.
542 if( mbGroupHasFocus )
544 uno::Any aOldCursor;
546 // #i13705# The old cursor can only contain valid
547 // values if it's the same paragraph!
548 if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND &&
549 maLastSelection.nEndPara == aSelection.nEndPara )
551 aOldCursor <<= maLastSelection.nEndPos;
553 else
555 aOldCursor <<= static_cast<sal_Int32>(-1);
558 maParaManager.FireEvent( aSelection.nEndPara,
559 aSelection.nEndPara+1,
560 AccessibleEventId::CARET_CHANGED,
561 uno::Any(aSelection.nEndPos),
562 aOldCursor );
565 SAL_INFO(
566 "svx",
567 "caret changed, Object: " << this << ", New pos: "
568 << aSelection.nEndPos << ", Old pos: "
569 << maLastSelection.nEndPos << ", New para: "
570 << aSelection.nEndPara << ", Old para: "
571 << maLastSelection.nEndPara);
573 // #108947# Sort new range before calling FireEvent
574 ::std::pair<sal_Int32, sal_Int32> sortedSelection(
575 makeSortedPair(::std::min( aSelection.nStartPara, nMaxValidParaIndex ),
576 ::std::min( aSelection.nEndPara, nMaxValidParaIndex ) ) );
578 // #108947# Sort last range before calling FireEvent
579 ::std::pair<sal_Int32, sal_Int32> sortedLastSelection(
580 makeSortedPair(::std::min( maLastSelection.nStartPara, nMaxValidParaIndex ),
581 ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ) ) );
583 // event TEXT_SELECTION_CHANGED has to be submitted. (#i27299#)
584 const sal_Int16 nTextSelChgEventId =
585 AccessibleEventId::TEXT_SELECTION_CHANGED;
586 // #107037# notify selection change
587 if( maLastSelection.nStartPara == EE_PARA_NOT_FOUND )
589 // last selection is undefined
590 // use method <ESelection::HasRange()> (#i27299#)
591 if ( aSelection.HasRange() )
593 // selection was undefined, now is on
594 maParaManager.FireEvent( sortedSelection.first,
595 sortedSelection.second+1,
596 nTextSelChgEventId );
599 else
601 // last selection is valid
602 // use method <ESelection::HasRange()> (#i27299#)
603 if ( maLastSelection.HasRange() &&
604 !aSelection.HasRange() )
606 // selection was on, now is empty
607 maParaManager.FireEvent( sortedLastSelection.first,
608 sortedLastSelection.second+1,
609 nTextSelChgEventId );
611 // use method <ESelection::HasRange()> (#i27299#)
612 else if( !maLastSelection.HasRange() &&
613 aSelection.HasRange() )
615 // selection was empty, now is on
616 maParaManager.FireEvent( sortedSelection.first,
617 sortedSelection.second+1,
618 nTextSelChgEventId );
620 // no event TEXT_SELECTION_CHANGED event, if new and
621 // last selection are empty. (#i27299#)
622 else if ( maLastSelection.HasRange() &&
623 aSelection.HasRange() )
625 // use sorted last and new selection
626 ESelection aTmpLastSel( maLastSelection );
627 aTmpLastSel.Adjust();
628 ESelection aTmpSel( aSelection );
629 aTmpSel.Adjust();
630 // first submit event for new and changed selection
631 sal_Int32 nPara = aTmpSel.nStartPara;
632 for ( ; nPara <= aTmpSel.nEndPara; ++nPara )
634 if ( nPara < aTmpLastSel.nStartPara ||
635 nPara > aTmpLastSel.nEndPara )
637 // new selection on paragraph <nPara>
638 maParaManager.FireEvent( nPara,
639 nTextSelChgEventId );
641 else
643 // check for changed selection on paragraph <nPara>
644 const sal_Int32 nParaStartPos =
645 nPara == aTmpSel.nStartPara
646 ? aTmpSel.nStartPos : 0;
647 const sal_Int32 nParaEndPos =
648 nPara == aTmpSel.nEndPara
649 ? aTmpSel.nEndPos : -1;
650 const sal_Int32 nLastParaStartPos =
651 nPara == aTmpLastSel.nStartPara
652 ? aTmpLastSel.nStartPos : 0;
653 const sal_Int32 nLastParaEndPos =
654 nPara == aTmpLastSel.nEndPara
655 ? aTmpLastSel.nEndPos : -1;
656 if ( nParaStartPos != nLastParaStartPos ||
657 nParaEndPos != nLastParaEndPos )
659 maParaManager.FireEvent(
660 nPara, nTextSelChgEventId );
664 // second submit event for 'old' selections
665 nPara = aTmpLastSel.nStartPara;
666 for ( ; nPara <= aTmpLastSel.nEndPara; ++nPara )
668 if ( nPara < aTmpSel.nStartPara ||
669 nPara > aTmpSel.nEndPara )
671 maParaManager.FireEvent( nPara,
672 nTextSelChgEventId );
678 maLastSelection = aSelection;
682 // no selection? no update actions
683 catch( const uno::RuntimeException& ) {}
686 void AccessibleTextHelper_Impl::ShutdownEditSource()
688 // This should only be called with solar mutex locked, i.e. from the main office thread
690 // This here is somewhat clumsy: As soon as our children have
691 // a NULL EditSource (maParaManager.SetEditSource()), they
692 // enter the disposed state and cannot be reanimated. Thus, it
693 // is unavoidable and a hard requirement to let go and create
694 // from scratch each and every child.
696 // invalidate children
697 maParaManager.Dispose();
698 maParaManager.SetNum(0);
700 // lost all children
701 if( mxFrontEnd.is() )
702 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
704 // quit listen on stale edit source
705 if( maEditSource.IsValid() )
706 EndListening( maEditSource.GetBroadcaster() );
708 maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
711 void AccessibleTextHelper_Impl::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
713 // This should only be called with solar mutex locked, i.e. from the main office thread
715 // shutdown old edit source
716 ShutdownEditSource();
718 // set new edit source
719 maEditSource.SetEditSource( std::move(pEditSource) );
721 // init child vector to the current child count
722 if( maEditSource.IsValid() )
724 maParaManager.SetNum( GetTextForwarder().GetParagraphCount() );
726 // listen on new edit source
727 StartListening( maEditSource.GetBroadcaster() );
729 UpdateVisibleChildren();
733 void AccessibleTextHelper_Impl::SetOffset( const Point& rPoint )
735 // guard against non-atomic access to maOffset data structure
737 std::scoped_lock aGuard( maMutex );
738 maOffset = rPoint;
741 maParaManager.SetEEOffset( rPoint );
743 // in all cases, check visibility afterwards.
744 UpdateVisibleChildren();
745 UpdateBoundRect();
748 void AccessibleTextHelper_Impl::UpdateVisibleChildren( bool bBroadcastEvents )
752 SvxTextForwarder& rCacheTF = GetTextForwarder();
753 sal_Int32 nParas=rCacheTF.GetParagraphCount();
755 // GetTextForwarder might have replaced everything, update
756 // paragraph count in case it's outdated
757 maParaManager.SetNum( nParas );
759 mnFirstVisibleChild = -1;
760 mnLastVisibleChild = -2;
762 for( sal_Int32 nCurrPara=0; nCurrPara<nParas; ++nCurrPara )
764 if (nCurrPara == 0)
765 mnFirstVisibleChild = nCurrPara;
766 mnLastVisibleChild = nCurrPara;
767 if (mxFrontEnd.is() && bBroadcastEvents)
769 // child not yet created?
770 if (!maParaManager.HasCreatedChild(nCurrPara))
772 GotPropertyEvent( uno::Any( maParaManager.CreateChild( nCurrPara - mnFirstVisibleChild,
773 mxFrontEnd, GetEditSource(), nCurrPara ).first ),
774 AccessibleEventId::CHILD );
779 catch( const uno::Exception& )
781 OSL_FAIL("AccessibleTextHelper_Impl::UpdateVisibleChildren error while determining visible children");
783 // something failed - currently no children
784 mnFirstVisibleChild = -1;
785 mnLastVisibleChild = -2;
786 maParaManager.SetNum(0);
788 // lost all children
789 if( bBroadcastEvents )
790 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
794 void AccessibleTextHelper_Impl::UpdateBoundRect()
796 // send BOUNDRECT_CHANGED to affected children
797 for(auto it = maParaManager.begin(); it != maParaManager.end(); ++it)
799 ::accessibility::AccessibleParaManager::WeakChild& rChild = *it;
800 // retrieve hard reference from weak one
801 auto aHardRef( rChild.first.get() );
803 if( aHardRef.is() )
805 awt::Rectangle aNewRect = aHardRef->getBounds();
806 const awt::Rectangle& aOldRect = rChild.second;
808 if( aNewRect.X != aOldRect.X ||
809 aNewRect.Y != aOldRect.Y ||
810 aNewRect.Width != aOldRect.Width ||
811 aNewRect.Height != aOldRect.Height )
813 // visible data changed
814 aHardRef->FireEvent( AccessibleEventId::BOUNDRECT_CHANGED );
816 // update internal bounds
817 rChild = ::accessibility::AccessibleParaManager::WeakChild( rChild.first, aNewRect );
823 #ifdef DBG_UTIL
824 void AccessibleTextHelper_Impl::CheckInvariants() const
826 if( mnFirstVisibleChild >= 0 &&
827 mnFirstVisibleChild > mnLastVisibleChild )
829 OSL_FAIL( "AccessibleTextHelper: range invalid" );
832 #endif
834 namespace {
836 // functor for sending child events (no stand-alone function, they are maybe not inlined)
837 class AccessibleTextHelper_LostChildEvent
839 public:
840 explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {}
841 void operator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara )
843 // retrieve hard reference from weak one
844 auto aHardRef( rPara.first.get() );
846 if( aHardRef.is() )
847 mrImpl.FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::Any(css::uno::Reference<css::accessibility::XAccessible>(aHardRef)) );
850 private:
851 AccessibleTextHelper_Impl& mrImpl;
856 void AccessibleTextHelper_Impl::ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast )
858 const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
860 /* rotate paragraphs
861 * =================
863 * Three cases:
865 * 1.
866 * ... nParagraph ... nParam1 ... nParam2 ...
867 * |______________[xxxxxxxxxxx]
868 * becomes
869 * [xxxxxxxxxxx]|______________
871 * tail is 0
873 * 2.
874 * ... nParam1 ... nParagraph ... nParam2 ...
875 * [xxxxxxxxxxx|xxxxxxxxxxxxxx]____________
876 * becomes
877 * ____________[xxxxxxxxxxx|xxxxxxxxxxxxxx]
879 * tail is nParagraph - nParam1
881 * 3.
882 * ... nParam1 ... nParam2 ... nParagraph ...
883 * [xxxxxxxxxxx]___________|____________
884 * becomes
885 * ___________|____________[xxxxxxxxxxx]
887 * tail is nParam2 - nParam1
890 // sort nParagraph, nParam1 and nParam2 in ascending order, calc range
891 if( nMiddle < nFirst )
893 ::std::swap(nFirst, nMiddle);
895 else if( nMiddle < nLast )
897 nLast = nLast + nMiddle - nFirst;
899 else
901 ::std::swap(nMiddle, nLast);
902 nLast = nLast + nMiddle - nFirst;
905 if( !(nFirst < nParas && nMiddle < nParas && nLast < nParas) )
906 return;
908 // since we have no "paragraph index
909 // changed" event on UAA, remove
910 // [first,last] and insert again later (in
911 // UpdateVisibleChildren)
913 // maParaManager.Rotate( nFirst, nMiddle, nLast );
915 // send CHILD_EVENT to affected children
916 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
917 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
919 ::std::advance( begin, nFirst );
920 ::std::advance( end, nLast+1 );
922 // TODO: maybe optimize here in the following way. If the
923 // number of removed children exceeds a certain threshold,
924 // use InvalidateFlags::Children
925 AccessibleTextHelper_LostChildEvent aFunctor( *this );
927 ::std::for_each( begin, end, aFunctor );
929 maParaManager.Release(nFirst, nLast+1);
930 // should be no need for UpdateBoundRect, since all affected children are cleared.
933 namespace {
935 // functor for sending child events (no stand-alone function, they are maybe not inlined)
936 class AccessibleTextHelper_ChildrenTextChanged
938 public:
939 void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
941 rPara.TextChanged();
945 /** functor processing queue events
947 Reacts on SfxHintId::TextParaInserted/REMOVED events and stores
948 their content
950 class AccessibleTextHelper_QueueFunctor
952 public:
953 AccessibleTextHelper_QueueFunctor() :
954 mnParasChanged( 0 ),
955 mnParaIndex(-1),
956 mnHintId(SfxHintId::NONE)
958 void operator()( const SfxHint* pEvent )
960 if( !pEvent || mnParasChanged == -1 )
961 return;
963 // determine hint type
964 const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent );
965 const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent );
967 if( !(!pEditSourceHint && pTextHint &&
968 (pTextHint->GetId() == SfxHintId::TextParaInserted ||
969 pTextHint->GetId() == SfxHintId::TextParaRemoved )) )
970 return;
972 if( pTextHint->GetValue() == EE_PARA_ALL )
974 mnParasChanged = -1;
976 else
978 mnHintId = pTextHint->GetId();
979 mnParaIndex = pTextHint->GetValue();
980 ++mnParasChanged;
984 /** Query number of paragraphs changed during queue processing.
986 @return number of changed paragraphs, -1 for
987 "every paragraph changed"
989 sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; }
990 /** Query index of last added/removed paragraph
992 @return index of lastly added paragraphs, -1 for none
993 added so far.
995 sal_Int32 GetParaIndex() const { return mnParaIndex; }
996 /** Query hint id of last interesting event
998 @return hint id of last interesting event (REMOVED/INSERTED).
1000 SfxHintId GetHintId() const { return mnHintId; }
1002 private:
1003 /** number of paragraphs changed during queue processing. -1 for
1004 "every paragraph changed"
1006 sal_Int32 mnParasChanged;
1007 /// index of paragraph added/removed last
1008 sal_Int32 mnParaIndex;
1009 /// TextHint ID (removed/inserted) of last interesting event
1010 SfxHintId mnHintId;
1015 void AccessibleTextHelper_Impl::ProcessQueue()
1017 // inspect queue for paragraph insert/remove events. If there
1018 // is exactly _one_ of those in the queue, and the number of
1019 // paragraphs has changed by exactly one, use that event to
1020 // determine a priori which paragraph was added/removed. This
1021 // is necessary, since I must sync right here with the
1022 // EditEngine state (number of paragraphs etc.), since I'm
1023 // potentially sending listener events right away.
1024 AccessibleTextHelper_QueueFunctor aFunctor;
1025 maEventQueue.ForEach( aFunctor );
1027 const sal_Int32 nNewParas( GetTextForwarder().GetParagraphCount() );
1028 const sal_Int32 nCurrParas( maParaManager.GetNum() );
1030 // whether every paragraph already is updated (no need to
1031 // repeat that later on, e.g. for PARA_MOVED events)
1032 bool bEverythingUpdated( false );
1034 if( std::abs( nNewParas - nCurrParas ) == 1 &&
1035 aFunctor.GetNumberOfParasChanged() == 1 )
1037 // #103483# Exactly one paragraph added/removed. This is
1038 // the normal case, optimize event handling here.
1040 if( aFunctor.GetHintId() == SfxHintId::TextParaInserted )
1042 // update num of paras
1043 maParaManager.SetNum( nNewParas );
1045 // release everything from the insertion position until the end
1046 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1048 // TODO: Clarify whether this behaviour _really_ saves
1049 // anybody anything!
1050 // update children, _don't_ broadcast
1051 UpdateVisibleChildren( false );
1052 UpdateBoundRect();
1054 // send insert event
1055 // #109864# Enforce creation of this paragraph
1058 GotPropertyEvent( uno::Any( getAccessibleChild( aFunctor.GetParaIndex() -
1059 mnFirstVisibleChild + GetStartIndex() ) ),
1060 AccessibleEventId::CHILD );
1062 catch( const uno::Exception& )
1064 OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph");
1067 else if( aFunctor.GetHintId() == SfxHintId::TextParaRemoved )
1069 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
1070 ::std::advance( begin, aFunctor.GetParaIndex() );
1071 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
1072 ::std::advance( end, 1 );
1074 // #i61812# remember para to be removed for later notification
1075 // AFTER the new state is applied (that after the para got removed)
1076 ::uno::Reference< XAccessible > xPara(begin->first.get());
1078 // release everything from the remove position until the end
1079 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1081 // update num of paras
1082 maParaManager.SetNum( nNewParas );
1084 // TODO: Clarify whether this behaviour _really_ saves
1085 // anybody anything!
1086 // update children, _don't_ broadcast
1087 UpdateVisibleChildren( false );
1088 UpdateBoundRect();
1090 // #i61812# notification for removed para
1091 if (xPara.is())
1092 FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::Any( xPara) );
1094 #ifdef DBG_UTIL
1095 else
1096 OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue() invalid hint id");
1097 #endif
1099 else if( nNewParas != nCurrParas )
1101 // release all paras
1102 maParaManager.Release(0, nCurrParas);
1104 // update num of paras
1105 maParaManager.SetNum( nNewParas );
1107 // #109864# create from scratch, don't broadcast
1108 UpdateVisibleChildren( false );
1109 UpdateBoundRect();
1111 // number of paragraphs somehow changed - but we have no
1112 // chance determining how. Thus, throw away everything and
1113 // create from scratch.
1114 // (child events should be broadcast after the changes are done...)
1115 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
1117 // no need for further updates later on
1118 bEverythingUpdated = true;
1121 bool bUpdatedBoundRectAndVisibleChildren(false);
1123 while( !maEventQueue.IsEmpty() )
1125 ::std::unique_ptr< SfxHint > pHint( maEventQueue.PopFront() );
1126 if (pHint)
1128 const SfxHint& rHint = *pHint;
1130 // Note, if you add events here, you need to update the AccessibleTextEventQueue::Append
1131 // code, because only the events we process here, are actually queued there.
1136 if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1138 const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1140 switch( pSdrHint->GetKind() )
1142 case SdrHintKind::BeginEdit:
1144 if(!IsActive())
1146 break;
1148 // change children state
1149 maParaManager.SetActive();
1151 // per definition, edit mode text has the focus
1152 SetFocus( true );
1153 break;
1156 case SdrHintKind::EndEdit:
1158 // focused child now loses focus
1159 ESelection aSelection;
1160 if( GetEditViewForwarder().GetSelection( aSelection ) )
1161 SetChildFocus( aSelection.nEndPara, false );
1163 // change children state
1164 maParaManager.SetActive( false );
1166 maLastSelection = ESelection( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND,
1167 EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND);
1168 break;
1170 default:
1171 break;
1174 else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1176 switch( pEditSourceHint->GetId() )
1178 case SfxHintId::EditSourceParasMoved:
1180 DBG_ASSERT( pEditSourceHint->GetStartValue() < GetTextForwarder().GetParagraphCount() &&
1181 pEditSourceHint->GetEndValue() < GetTextForwarder().GetParagraphCount(),
1182 "AccessibleTextHelper_Impl::NotifyHdl: Invalid notification");
1184 if( !bEverythingUpdated )
1186 ParagraphsMoved(pEditSourceHint->GetStartValue(),
1187 pEditSourceHint->GetValue(),
1188 pEditSourceHint->GetEndValue());
1190 // in all cases, check visibility afterwards.
1191 UpdateVisibleChildren();
1193 break;
1196 case SfxHintId::EditSourceSelectionChanged:
1197 // notify listeners
1200 UpdateSelection();
1202 // maybe we're not in edit mode (this is not an error)
1203 catch( const uno::Exception& ) {}
1204 break;
1205 default: break;
1208 else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1210 const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
1212 switch( pTextHint->GetId() )
1214 case SfxHintId::TextModified:
1216 // notify listeners
1217 sal_Int32 nPara( pTextHint->GetValue() );
1219 // #108900# Delegate change event to children
1220 AccessibleTextHelper_ChildrenTextChanged aNotifyChildrenFunctor;
1222 if( nPara == EE_PARA_ALL )
1224 // #108900# Call every child
1225 ::std::for_each( maParaManager.begin(), maParaManager.end(),
1226 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1228 else
1229 if( nPara < nParas )
1231 // #108900# Call child at index nPara
1232 ::std::for_each( maParaManager.begin()+nPara, maParaManager.begin()+nPara+1,
1233 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1235 break;
1238 case SfxHintId::TextParaInserted:
1239 // already happened above
1240 break;
1242 case SfxHintId::TextParaRemoved:
1243 // already happened above
1244 break;
1246 case SfxHintId::TextHeightChanged:
1247 // visibility changed, done below
1248 break;
1250 case SfxHintId::TextViewScrolled:
1251 // visibility changed, done below
1252 break;
1253 default: break;
1256 // in all cases, check visibility afterwards.
1257 if (!bUpdatedBoundRectAndVisibleChildren)
1259 UpdateVisibleChildren();
1260 UpdateBoundRect();
1261 bUpdatedBoundRectAndVisibleChildren = true;
1264 else if (rHint.GetId() == SfxHintId::SvxViewChanged)
1266 // just check visibility
1267 if (!bUpdatedBoundRectAndVisibleChildren)
1269 UpdateVisibleChildren();
1270 UpdateBoundRect();
1271 bUpdatedBoundRectAndVisibleChildren = true;
1274 // it's VITAL to keep the SfxSimpleHint last! It's the base of some classes above!
1275 else if( rHint.GetId() == SfxHintId::Dying)
1277 // edit source is dying under us, become defunc then
1280 // make edit source inaccessible
1281 // Note: cannot destroy it here, since we're called from there!
1282 ShutdownEditSource();
1284 catch( const uno::Exception& ) {}
1287 catch( const uno::Exception& )
1289 DBG_UNHANDLED_EXCEPTION("svx");
1295 void AccessibleTextHelper_Impl::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
1297 // precondition: solar mutex locked
1298 DBG_TESTSOLARMUTEX();
1300 // precondition: not in a recursion
1301 if( mbInNotify )
1302 return;
1304 mbInNotify = true;
1308 // Process notification event, arranged in order of likelihood of
1309 // occurrence to avoid unnecessary dynamic_cast. Note that
1310 // SvxEditSourceHint is derived from TextHint, so has to be checked
1311 // before that.
1312 if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1314 const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1315 // process drawing layer events right away, if not
1316 // within an open EE notification frame. Otherwise,
1317 // event processing would be delayed until next EE
1318 // notification sequence.
1319 maEventQueue.Append( *pSdrHint );
1321 else if (rHint.GetId() == SfxHintId::SvxViewChanged)
1323 const SvxViewChangedHint* pViewHint = static_cast<const SvxViewChangedHint*>(&rHint);
1324 // process visibility right away, if not within an
1325 // open EE notification frame. Otherwise, event
1326 // processing would be delayed until next EE
1327 // notification sequence.
1328 maEventQueue.Append( *pViewHint );
1330 else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1332 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1333 maEventQueue.Append( *pEditSourceHint );
1335 else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1337 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1338 if(pTextHint->GetId() == SfxHintId::TextProcessNotifications)
1339 ProcessQueue();
1340 else
1341 maEventQueue.Append( *pTextHint );
1343 // it's VITAL to keep the SfxHint last! It's the base of the classes above!
1344 else if( rHint.GetId() == SfxHintId::Dying )
1346 // handle this event _at once_, because after that, objects are invalid
1347 // edit source is dying under us, become defunc then
1348 maEventQueue.Clear();
1351 // make edit source inaccessible
1352 // Note: cannot destroy it here, since we're called from there!
1353 ShutdownEditSource();
1355 catch( const uno::Exception& ) {}
1358 catch( const uno::Exception& )
1360 DBG_UNHANDLED_EXCEPTION("svx");
1361 mbInNotify = false;
1364 mbInNotify = false;
1367 void AccessibleTextHelper_Impl::Dispose()
1369 if( getNotifierClientId() != snNotifierClientRevoked)
1373 // #106234# Unregister from EventNotifier
1374 ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() );
1375 SAL_INFO("svx", "disposed ID: " << mnNotifierClientId );
1377 catch( const uno::Exception& ) {}
1379 mnNotifierClientId = snNotifierClientRevoked;
1384 // dispose children
1385 maParaManager.Dispose();
1387 catch( const uno::Exception& ) {}
1389 // quit listen on stale edit source
1390 if( maEditSource.IsValid() )
1391 EndListening( maEditSource.GetBroadcaster() );
1393 // clear references
1394 maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
1395 mxFrontEnd = nullptr;
1398 void AccessibleTextHelper_Impl::FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const
1400 // -- object locked --
1401 AccessibleEventObject aEvent;
1403 std::scoped_lock aGuard(maMutex);
1405 DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper::FireEvent: no event source set");
1407 if (mxFrontEnd.is())
1408 aEvent = AccessibleEventObject(mxFrontEnd->getAccessibleContext(), nEventId,
1409 rNewValue, rOldValue, -1);
1410 else
1411 aEvent = AccessibleEventObject(uno::Reference<uno::XInterface>(), nEventId,
1412 rNewValue, rOldValue, -1);
1414 // no locking necessary, FireEvent internally copies listeners
1415 // if someone removes/adds in between Further locking,
1416 // actually, might lead to deadlocks, since we're calling out
1417 // of this object
1419 // -- until here --
1421 FireEvent(aEvent);
1424 void AccessibleTextHelper_Impl::FireEvent( const AccessibleEventObject& rEvent ) const
1426 // #106234# Delegate to EventNotifier
1427 if (getNotifierClientId() != snNotifierClientRevoked)
1428 ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(), rEvent );
1431 // XAccessibleContext
1432 sal_Int64 AccessibleTextHelper_Impl::getAccessibleChildCount() const
1434 return mnLastVisibleChild - mnFirstVisibleChild + 1;
1437 uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int64 i )
1439 i -= GetStartIndex();
1441 if( 0 > i || i >= getAccessibleChildCount() ||
1442 GetTextForwarder().GetParagraphCount() <= i )
1444 throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd);
1447 DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set");
1449 if( mxFrontEnd.is() )
1450 return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first;
1451 else
1452 return nullptr;
1455 void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1457 if( getNotifierClientId() != snNotifierClientRevoked )
1458 ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener );
1461 void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1463 if( getNotifierClientId() == snNotifierClientRevoked )
1464 return;
1466 const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener );
1467 if ( !nListenerCount )
1469 // no listeners anymore
1470 // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client),
1471 // and at least to us not firing any events anymore, in case somebody calls
1472 // NotifyAccessibleEvent, again
1473 ::comphelper::AccessibleEventNotifier::TClientId nId( getNotifierClientId() );
1474 mnNotifierClientId = snNotifierClientRevoked;
1475 ::comphelper::AccessibleEventNotifier::revokeClient( nId );
1479 uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint )
1481 // make given position relative
1482 if( !mxFrontEnd.is() )
1483 throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1485 uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext();
1487 if( !xFrontEndContext.is() )
1488 throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1490 uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW );
1492 // #103862# No longer need to make given position relative
1493 Point aPoint( _aPoint.X, _aPoint.Y );
1495 // respect EditEngine offset to surrounding shape/cell
1496 aPoint -= GetOffset();
1498 // convert to EditEngine coordinate system
1499 SvxTextForwarder& rCacheTF = GetTextForwarder();
1500 Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) );
1502 // iterate over all visible children (including those not yet created)
1503 sal_Int64 nChild;
1504 for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild )
1506 DBG_ASSERT(nChild >= 0,
1507 "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow");
1509 tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) );
1511 if( aParaBounds.Contains( aLogPoint ) )
1512 return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() );
1515 // found none
1516 return nullptr;
1520 // AccessibleTextHelper implementation (simply forwards to impl)
1522 AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) :
1523 mpImpl( new AccessibleTextHelper_Impl() )
1525 SolarMutexGuard aGuard;
1527 SetEditSource( std::move(pEditSource) );
1530 AccessibleTextHelper::~AccessibleTextHelper()
1534 const SvxEditSource& AccessibleTextHelper::GetEditSource() const
1536 #ifdef DBG_UTIL
1537 mpImpl->CheckInvariants();
1539 const SvxEditSource& aEditSource = mpImpl->GetEditSource();
1541 mpImpl->CheckInvariants();
1543 return aEditSource;
1544 #else
1545 return mpImpl->GetEditSource();
1546 #endif
1549 void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
1551 #ifdef DBG_UTIL
1552 // precondition: solar mutex locked
1553 DBG_TESTSOLARMUTEX();
1555 mpImpl->CheckInvariants();
1556 #endif
1558 mpImpl->SetEditSource( std::move(pEditSource) );
1560 #ifdef DBG_UTIL
1561 mpImpl->CheckInvariants();
1562 #endif
1565 void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface )
1567 #ifdef DBG_UTIL
1568 mpImpl->CheckInvariants();
1569 #endif
1571 mpImpl->SetEventSource( rInterface );
1573 #ifdef DBG_UTIL
1574 mpImpl->CheckInvariants();
1575 #endif
1578 void AccessibleTextHelper::SetFocus( bool bHaveFocus )
1580 #ifdef DBG_UTIL
1581 // precondition: solar mutex locked
1582 DBG_TESTSOLARMUTEX();
1584 mpImpl->CheckInvariants();
1585 #endif
1587 mpImpl->SetFocus( bHaveFocus );
1589 #ifdef DBG_UTIL
1590 mpImpl->CheckInvariants();
1591 #endif
1594 bool AccessibleTextHelper::HaveFocus()
1596 #ifdef DBG_UTIL
1597 mpImpl->CheckInvariants();
1599 bool bRet( mpImpl->HaveFocus() );
1601 mpImpl->CheckInvariants();
1603 return bRet;
1604 #else
1605 return mpImpl->HaveFocus();
1606 #endif
1609 void AccessibleTextHelper::SetOffset( const Point& rPoint )
1611 #ifdef DBG_UTIL
1612 // precondition: solar mutex locked
1613 DBG_TESTSOLARMUTEX();
1615 mpImpl->CheckInvariants();
1616 #endif
1618 mpImpl->SetOffset( rPoint );
1620 #ifdef DBG_UTIL
1621 mpImpl->CheckInvariants();
1622 #endif
1625 void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset )
1627 #ifdef DBG_UTIL
1628 // precondition: solar mutex locked
1629 DBG_TESTSOLARMUTEX();
1631 mpImpl->CheckInvariants();
1632 #endif
1634 mpImpl->SetStartIndex( nOffset );
1636 #ifdef DBG_UTIL
1637 mpImpl->CheckInvariants();
1638 #endif
1641 sal_Int32 AccessibleTextHelper::GetStartIndex() const
1643 #ifdef DBG_UTIL
1644 mpImpl->CheckInvariants();
1646 sal_Int32 nOffset = mpImpl->GetStartIndex();
1648 mpImpl->CheckInvariants();
1650 return nOffset;
1651 #else
1652 return mpImpl->GetStartIndex();
1653 #endif
1656 void AccessibleTextHelper::SetAdditionalChildStates( sal_Int64 nChildStates )
1658 mpImpl->SetAdditionalChildStates( nChildStates );
1661 void AccessibleTextHelper::UpdateChildren()
1663 #ifdef DBG_UTIL
1664 // precondition: solar mutex locked
1665 DBG_TESTSOLARMUTEX();
1667 mpImpl->CheckInvariants();
1668 #endif
1670 mpImpl->UpdateVisibleChildren();
1671 mpImpl->UpdateBoundRect();
1673 mpImpl->UpdateSelection();
1675 #ifdef DBG_UTIL
1676 mpImpl->CheckInvariants();
1677 #endif
1680 void AccessibleTextHelper::Dispose()
1682 // As Dispose calls ShutdownEditSource, which in turn
1683 // deregisters as listener on the edit source, have to lock
1684 // here
1685 SolarMutexGuard aGuard;
1687 #ifdef DBG_UTIL
1688 mpImpl->CheckInvariants();
1689 #endif
1691 mpImpl->Dispose();
1693 #ifdef DBG_UTIL
1694 mpImpl->CheckInvariants();
1695 #endif
1698 // XAccessibleContext
1699 sal_Int64 AccessibleTextHelper::GetChildCount() const
1701 SolarMutexGuard aGuard;
1703 #ifdef DBG_UTIL
1704 mpImpl->CheckInvariants();
1706 sal_Int64 nRet = mpImpl->getAccessibleChildCount();
1708 mpImpl->CheckInvariants();
1710 return nRet;
1711 #else
1712 return mpImpl->getAccessibleChildCount();
1713 #endif
1716 uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int64 i )
1718 SolarMutexGuard aGuard;
1720 #ifdef DBG_UTIL
1721 mpImpl->CheckInvariants();
1723 uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i );
1725 mpImpl->CheckInvariants();
1727 return xRet;
1728 #else
1729 return mpImpl->getAccessibleChild( i );
1730 #endif
1733 void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1735 #ifdef DBG_UTIL
1736 mpImpl->CheckInvariants();
1738 mpImpl->addAccessibleEventListener( xListener );
1740 mpImpl->CheckInvariants();
1741 #else
1742 mpImpl->addAccessibleEventListener( xListener );
1743 #endif
1746 void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1748 #ifdef DBG_UTIL
1749 mpImpl->CheckInvariants();
1751 mpImpl->removeAccessibleEventListener( xListener );
1753 mpImpl->CheckInvariants();
1754 #else
1755 mpImpl->removeAccessibleEventListener( xListener );
1756 #endif
1759 // XAccessibleComponent
1760 uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint )
1762 SolarMutexGuard aGuard;
1764 #ifdef DBG_UTIL
1765 mpImpl->CheckInvariants();
1767 uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint );
1769 mpImpl->CheckInvariants();
1771 return xChild;
1772 #else
1773 return mpImpl->getAccessibleAtPoint( aPoint );
1774 #endif
1777 } // end of namespace accessibility
1780 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */