bump product version to 7.2.5.1
[LibreOffice.git] / svx / source / accessibility / AccessibleTextHelper.cxx
blob5be7ae3b3ab761aa29eab15ef784828bd8eff038
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 <utility>
25 #include <algorithm>
26 #include <osl/mutex.hxx>
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 <tools/diagnose_ex.h>
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_Int32 getAccessibleChildCount() const;
91 uno::Reference< XAccessible > getAccessibleChild( sal_Int32 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 ::osl::MutexGuard 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( const VectorOfStates& rChildStates );
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()
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 int 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 ::osl::Mutex maMutex;
221 /// our current offset to the containing shape/cell (guarded by maMutex)
222 Point maOffset;
224 /// client Id from AccessibleEventNotifier
225 int mnNotifierClientId;
228 AccessibleTextHelper_Impl::AccessibleTextHelper_Impl() :
229 maLastSelection( EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND,EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND ),
230 mnFirstVisibleChild( -1 ),
231 mnLastVisibleChild( -2 ),
232 mnStartIndex( 0 ),
233 mbInNotify( false ),
234 mbGroupHasFocus( false ),
235 mbThisHasFocus( false ),
236 maOffset(0,0),
237 // well, that's strictly exception safe, though not really
238 // robust. We rely on the fact that this member is constructed
239 // last, and that the constructor body is empty, thus no
240 // chance for exceptions once the Id is fetched. Nevertheless,
241 // normally should employ RAII here...
242 mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient())
244 SAL_INFO("svx", "received ID: " << mnNotifierClientId );
247 AccessibleTextHelper_Impl::~AccessibleTextHelper_Impl()
249 SolarMutexGuard aGuard;
253 // call Dispose here, too, since we've some resources not
254 // automatically freed otherwise
255 Dispose();
257 catch( const uno::Exception& ) {}
260 SvxTextForwarder& AccessibleTextHelper_Impl::GetTextForwarder() const
262 if( !maEditSource.IsValid() )
263 throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
265 SvxTextForwarder* pTextForwarder = maEditSource.GetTextForwarder();
267 if( !pTextForwarder )
268 throw uno::RuntimeException("Unable to fetch text forwarder, model might be dead", mxFrontEnd);
270 if( !pTextForwarder->IsValid() )
271 throw uno::RuntimeException("Text forwarder is invalid, model might be dead", mxFrontEnd);
273 return *pTextForwarder;
276 SvxViewForwarder& AccessibleTextHelper_Impl::GetViewForwarder() const
278 if( !maEditSource.IsValid() )
279 throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
281 SvxViewForwarder* pViewForwarder = maEditSource.GetViewForwarder();
283 if( !pViewForwarder )
284 throw uno::RuntimeException("Unable to fetch view forwarder, model might be dead", mxFrontEnd);
286 if( !pViewForwarder->IsValid() )
287 throw uno::RuntimeException("View forwarder is invalid, model might be dead", mxFrontEnd);
289 return *pViewForwarder;
292 SvxEditViewForwarder& AccessibleTextHelper_Impl::GetEditViewForwarder() const
294 if( !maEditSource.IsValid() )
295 throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
297 SvxEditViewForwarder* pViewForwarder = maEditSource.GetEditViewForwarder();
299 if( !pViewForwarder )
301 throw uno::RuntimeException("No edit view forwarder, object not in edit mode", mxFrontEnd);
304 if( !pViewForwarder->IsValid() )
306 throw uno::RuntimeException("View forwarder is invalid, object not in edit mode", mxFrontEnd);
309 return *pViewForwarder;
312 SvxEditSourceAdapter& AccessibleTextHelper_Impl::GetEditSource() const
314 if( !maEditSource.IsValid() )
315 throw uno::RuntimeException("AccessibleTextHelper_Impl::GetEditSource: no edit source", mxFrontEnd );
316 return maEditSource;
319 namespace {
321 // functor for sending child events (no stand-alone function, they are maybe not inlined)
322 class AccessibleTextHelper_OffsetChildIndex
324 public:
325 explicit AccessibleTextHelper_OffsetChildIndex( sal_Int32 nDifference ) : mnDifference(nDifference) {}
326 void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
328 rPara.SetIndexInParent( rPara.GetIndexInParent() + mnDifference );
331 private:
332 const sal_Int32 mnDifference;
337 void AccessibleTextHelper_Impl::SetStartIndex( sal_Int32 nOffset )
339 sal_Int32 nOldOffset( mnStartIndex );
341 mnStartIndex = nOffset;
343 if( nOldOffset != nOffset )
345 // update children
346 AccessibleTextHelper_OffsetChildIndex aFunctor( nOffset - nOldOffset );
348 ::std::for_each( maParaManager.begin(), maParaManager.end(),
349 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_OffsetChildIndex > (aFunctor) );
353 void AccessibleTextHelper_Impl::SetAdditionalChildStates( const VectorOfStates& rChildStates )
355 maParaManager.SetAdditionalChildStates( rChildStates );
358 void AccessibleTextHelper_Impl::SetChildFocus( sal_Int32 nChild, bool bHaveFocus )
360 if( bHaveFocus )
362 if( mbThisHasFocus )
363 SetShapeFocus( false );
365 maParaManager.SetFocus( nChild );
367 // we just received the focus, also send caret event then
368 UpdateSelection();
370 SAL_INFO("svx", "Paragraph " << nChild << " received focus");
372 else
374 maParaManager.SetFocus( -1 );
376 SAL_INFO("svx", "Paragraph " << nChild << " lost focus");
378 if( mbGroupHasFocus )
379 SetShapeFocus( true );
383 void AccessibleTextHelper_Impl::ChangeChildFocus( sal_Int32 nNewChild )
385 if( mbThisHasFocus )
386 SetShapeFocus( false );
388 mbGroupHasFocus = true;
389 maParaManager.SetFocus( nNewChild );
391 SAL_INFO("svx", "Paragraph " << nNewChild << " received focus");
394 void AccessibleTextHelper_Impl::SetShapeFocus( bool bHaveFocus )
396 bool bOldFocus( mbThisHasFocus );
398 mbThisHasFocus = bHaveFocus;
400 if( bOldFocus == bHaveFocus )
401 return;
403 if( bHaveFocus )
405 if( mxFrontEnd.is() )
407 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
408 if ( !pAccessibleCell )
409 GotPropertyEvent( uno::makeAny(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED );
410 else // the focus event on cell should be fired on table directly
412 AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable();
413 if (pAccTable)
414 pAccTable->SetStateDirectly(AccessibleStateType::FOCUSED);
417 SAL_INFO("svx", "Parent object received focus" );
419 else
421 // The focus state should be reset directly on table.
422 //LostPropertyEvent( uno::makeAny(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED );
423 if( mxFrontEnd.is() )
425 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
426 if ( !pAccessibleCell )
427 FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::makeAny(AccessibleStateType::FOCUSED) );
428 else
430 AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable();
431 if (pAccTable)
432 pAccTable->ResetStateDirectly(AccessibleStateType::FOCUSED);
435 SAL_INFO("svx", "Parent object lost focus" );
439 void AccessibleTextHelper_Impl::SetFocus( bool bHaveFocus )
441 bool bOldFocus( mbGroupHasFocus );
443 mbGroupHasFocus = bHaveFocus;
445 if( IsActive() )
449 // find the one with the cursor and get/set focus accordingly
450 ESelection aSelection;
451 if( GetEditViewForwarder().GetSelection( aSelection ) )
452 SetChildFocus( aSelection.nEndPara, bHaveFocus );
454 catch( const uno::Exception& ) {}
456 else if( bOldFocus != bHaveFocus )
458 SetShapeFocus( bHaveFocus );
461 SAL_INFO("svx", "focus changed, Object " << this << ", state: " << (bHaveFocus ? "focused" : "not focused") );
464 bool AccessibleTextHelper_Impl::IsActive() const
468 SvxEditSource& rEditSource = GetEditSource();
469 SvxEditViewForwarder* pViewForwarder = rEditSource.GetEditViewForwarder();
471 if( !pViewForwarder )
472 return false;
474 if( mxFrontEnd.is() )
476 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
477 if ( pAccessibleCell )
479 sdr::table::CellRef xCell = pAccessibleCell->getCellRef();
480 if ( xCell.is() )
481 return xCell->IsActiveCell();
484 return pViewForwarder->IsValid();
486 catch( const uno::RuntimeException& )
488 return false;
492 void AccessibleTextHelper_Impl::UpdateSelection()
496 ESelection aSelection;
497 if( GetEditViewForwarder().GetSelection( aSelection ) )
499 if( maLastSelection != aSelection &&
500 aSelection.nEndPara < maParaManager.GetNum() )
502 // #103998# Not that important, changed from assertion to trace
503 if( mbThisHasFocus )
505 SAL_INFO("svx", "Parent has focus!");
508 sal_Int32 nMaxValidParaIndex( GetTextForwarder().GetParagraphCount() - 1 );
510 // notify all affected paragraphs (TODO: may be suboptimal,
511 // since some paragraphs might stay selected)
512 if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND )
514 // Did the caret move from one paragraph to another?
515 // #100530# no caret events if not focused.
516 if( mbGroupHasFocus &&
517 maLastSelection.nEndPara != aSelection.nEndPara )
519 if( maLastSelection.nEndPara < maParaManager.GetNum() )
521 maParaManager.FireEvent( ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ),
522 ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex )+1,
523 AccessibleEventId::CARET_CHANGED,
524 uno::makeAny(static_cast<sal_Int32>(-1)),
525 uno::makeAny(maLastSelection.nEndPos) );
528 ChangeChildFocus( aSelection.nEndPara );
530 SAL_INFO(
531 "svx",
532 "focus changed, Object: " << this
533 << ", Paragraph: " << aSelection.nEndPara
534 << ", Last paragraph: "
535 << maLastSelection.nEndPara);
539 // #100530# no caret events if not focused.
540 if( mbGroupHasFocus )
542 uno::Any aOldCursor;
544 // #i13705# The old cursor can only contain valid
545 // values if it's the same paragraph!
546 if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND &&
547 maLastSelection.nEndPara == aSelection.nEndPara )
549 aOldCursor <<= maLastSelection.nEndPos;
551 else
553 aOldCursor <<= static_cast<sal_Int32>(-1);
556 maParaManager.FireEvent( aSelection.nEndPara,
557 aSelection.nEndPara+1,
558 AccessibleEventId::CARET_CHANGED,
559 uno::makeAny(aSelection.nEndPos),
560 aOldCursor );
563 SAL_INFO(
564 "svx",
565 "caret changed, Object: " << this << ", New pos: "
566 << aSelection.nEndPos << ", Old pos: "
567 << maLastSelection.nEndPos << ", New para: "
568 << aSelection.nEndPara << ", Old para: "
569 << maLastSelection.nEndPara);
571 // #108947# Sort new range before calling FireEvent
572 ::std::pair<sal_Int32, sal_Int32> sortedSelection(
573 makeSortedPair(::std::min( aSelection.nStartPara, nMaxValidParaIndex ),
574 ::std::min( aSelection.nEndPara, nMaxValidParaIndex ) ) );
576 // #108947# Sort last range before calling FireEvent
577 ::std::pair<sal_Int32, sal_Int32> sortedLastSelection(
578 makeSortedPair(::std::min( maLastSelection.nStartPara, nMaxValidParaIndex ),
579 ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ) ) );
581 // event TEXT_SELECTION_CHANGED has to be submitted. (#i27299#)
582 const sal_Int16 nTextSelChgEventId =
583 AccessibleEventId::TEXT_SELECTION_CHANGED;
584 // #107037# notify selection change
585 if( maLastSelection.nStartPara == EE_PARA_NOT_FOUND )
587 // last selection is undefined
588 // use method <ESelection::HasRange()> (#i27299#)
589 if ( aSelection.HasRange() )
591 // selection was undefined, now is on
592 maParaManager.FireEvent( sortedSelection.first,
593 sortedSelection.second+1,
594 nTextSelChgEventId );
597 else
599 // last selection is valid
600 // use method <ESelection::HasRange()> (#i27299#)
601 if ( maLastSelection.HasRange() &&
602 !aSelection.HasRange() )
604 // selection was on, now is empty
605 maParaManager.FireEvent( sortedLastSelection.first,
606 sortedLastSelection.second+1,
607 nTextSelChgEventId );
609 // use method <ESelection::HasRange()> (#i27299#)
610 else if( !maLastSelection.HasRange() &&
611 aSelection.HasRange() )
613 // selection was empty, now is on
614 maParaManager.FireEvent( sortedSelection.first,
615 sortedSelection.second+1,
616 nTextSelChgEventId );
618 // no event TEXT_SELECTION_CHANGED event, if new and
619 // last selection are empty. (#i27299#)
620 else if ( maLastSelection.HasRange() &&
621 aSelection.HasRange() )
623 // use sorted last and new selection
624 ESelection aTmpLastSel( maLastSelection );
625 aTmpLastSel.Adjust();
626 ESelection aTmpSel( aSelection );
627 aTmpSel.Adjust();
628 // first submit event for new and changed selection
629 sal_Int32 nPara = aTmpSel.nStartPara;
630 for ( ; nPara <= aTmpSel.nEndPara; ++nPara )
632 if ( nPara < aTmpLastSel.nStartPara ||
633 nPara > aTmpLastSel.nEndPara )
635 // new selection on paragraph <nPara>
636 maParaManager.FireEvent( nPara,
637 nTextSelChgEventId );
639 else
641 // check for changed selection on paragraph <nPara>
642 const sal_Int32 nParaStartPos =
643 nPara == aTmpSel.nStartPara
644 ? aTmpSel.nStartPos : 0;
645 const sal_Int32 nParaEndPos =
646 nPara == aTmpSel.nEndPara
647 ? aTmpSel.nEndPos : -1;
648 const sal_Int32 nLastParaStartPos =
649 nPara == aTmpLastSel.nStartPara
650 ? aTmpLastSel.nStartPos : 0;
651 const sal_Int32 nLastParaEndPos =
652 nPara == aTmpLastSel.nEndPara
653 ? aTmpLastSel.nEndPos : -1;
654 if ( nParaStartPos != nLastParaStartPos ||
655 nParaEndPos != nLastParaEndPos )
657 maParaManager.FireEvent(
658 nPara, nTextSelChgEventId );
662 // second submit event for 'old' selections
663 nPara = aTmpLastSel.nStartPara;
664 for ( ; nPara <= aTmpLastSel.nEndPara; ++nPara )
666 if ( nPara < aTmpSel.nStartPara ||
667 nPara > aTmpSel.nEndPara )
669 maParaManager.FireEvent( nPara,
670 nTextSelChgEventId );
676 maLastSelection = aSelection;
680 // no selection? no update actions
681 catch( const uno::RuntimeException& ) {}
684 void AccessibleTextHelper_Impl::ShutdownEditSource()
686 // This should only be called with solar mutex locked, i.e. from the main office thread
688 // This here is somewhat clumsy: As soon as our children have
689 // a NULL EditSource (maParaManager.SetEditSource()), they
690 // enter the disposed state and cannot be reanimated. Thus, it
691 // is unavoidable and a hard requirement to let go and create
692 // from scratch each and every child.
694 // invalidate children
695 maParaManager.Dispose();
696 maParaManager.SetNum(0);
698 // lost all children
699 if( mxFrontEnd.is() )
700 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
702 // quit listen on stale edit source
703 if( maEditSource.IsValid() )
704 EndListening( maEditSource.GetBroadcaster() );
706 maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
709 void AccessibleTextHelper_Impl::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
711 // This should only be called with solar mutex locked, i.e. from the main office thread
713 // shutdown old edit source
714 ShutdownEditSource();
716 // set new edit source
717 maEditSource.SetEditSource( std::move(pEditSource) );
719 // init child vector to the current child count
720 if( maEditSource.IsValid() )
722 maParaManager.SetNum( GetTextForwarder().GetParagraphCount() );
724 // listen on new edit source
725 StartListening( maEditSource.GetBroadcaster() );
727 UpdateVisibleChildren();
731 void AccessibleTextHelper_Impl::SetOffset( const Point& rPoint )
733 // guard against non-atomic access to maOffset data structure
735 ::osl::MutexGuard aGuard( maMutex );
736 maOffset = rPoint;
739 maParaManager.SetEEOffset( rPoint );
741 // in all cases, check visibility afterwards.
742 UpdateVisibleChildren();
743 UpdateBoundRect();
746 void AccessibleTextHelper_Impl::UpdateVisibleChildren( bool bBroadcastEvents )
750 SvxTextForwarder& rCacheTF = GetTextForwarder();
751 sal_Int32 nParas=rCacheTF.GetParagraphCount();
753 // GetTextForwarder might have replaced everything, update
754 // paragraph count in case it's outdated
755 maParaManager.SetNum( nParas );
757 mnFirstVisibleChild = -1;
758 mnLastVisibleChild = -2;
760 for( sal_Int32 nCurrPara=0; nCurrPara<nParas; ++nCurrPara )
762 if (nCurrPara == 0)
763 mnFirstVisibleChild = nCurrPara;
764 mnLastVisibleChild = nCurrPara;
765 if (mxFrontEnd.is() && bBroadcastEvents)
767 // child not yet created?
768 ::accessibility::AccessibleParaManager::WeakChild aChild( maParaManager.GetChild(nCurrPara) );
769 if( aChild.second.Width == 0 &&
770 aChild.second.Height == 0 )
772 GotPropertyEvent( uno::makeAny( 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 namespace {
796 // functor for checking changes in paragraph bounding boxes (no stand-alone function, maybe not inlined)
797 class AccessibleTextHelper_UpdateChildBounds
799 public:
800 explicit AccessibleTextHelper_UpdateChildBounds() {}
801 ::accessibility::AccessibleParaManager::WeakChild operator()( const ::accessibility::AccessibleParaManager::WeakChild& rChild )
803 // retrieve hard reference from weak one
804 auto aHardRef( rChild.first.get() );
806 if( aHardRef.is() )
808 awt::Rectangle aNewRect = aHardRef->getBounds();
809 const awt::Rectangle& aOldRect = rChild.second;
811 if( aNewRect.X != aOldRect.X ||
812 aNewRect.Y != aOldRect.Y ||
813 aNewRect.Width != aOldRect.Width ||
814 aNewRect.Height != aOldRect.Height )
816 // visible data changed
817 aHardRef->FireEvent( AccessibleEventId::BOUNDRECT_CHANGED );
819 // update internal bounds
820 return ::accessibility::AccessibleParaManager::WeakChild( rChild.first, aNewRect );
824 // identity transform
825 return rChild;
831 void AccessibleTextHelper_Impl::UpdateBoundRect()
833 // send BOUNDRECT_CHANGED to affected children
834 AccessibleTextHelper_UpdateChildBounds aFunctor;
835 ::std::transform( maParaManager.begin(), maParaManager.end(), maParaManager.begin(), aFunctor );
838 #ifdef DBG_UTIL
839 void AccessibleTextHelper_Impl::CheckInvariants() const
841 if( mnFirstVisibleChild >= 0 &&
842 mnFirstVisibleChild > mnLastVisibleChild )
844 OSL_FAIL( "AccessibleTextHelper: range invalid" );
847 #endif
849 namespace {
851 // functor for sending child events (no stand-alone function, they are maybe not inlined)
852 class AccessibleTextHelper_LostChildEvent
854 public:
855 explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {}
856 void operator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara )
858 // retrieve hard reference from weak one
859 auto aHardRef( rPara.first.get() );
861 if( aHardRef.is() )
862 mrImpl.FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny<css::uno::Reference<css::accessibility::XAccessible>>(aHardRef) );
865 private:
866 AccessibleTextHelper_Impl& mrImpl;
871 void AccessibleTextHelper_Impl::ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast )
873 const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
875 /* rotate paragraphs
876 * =================
878 * Three cases:
880 * 1.
881 * ... nParagraph ... nParam1 ... nParam2 ...
882 * |______________[xxxxxxxxxxx]
883 * becomes
884 * [xxxxxxxxxxx]|______________
886 * tail is 0
888 * 2.
889 * ... nParam1 ... nParagraph ... nParam2 ...
890 * [xxxxxxxxxxx|xxxxxxxxxxxxxx]____________
891 * becomes
892 * ____________[xxxxxxxxxxx|xxxxxxxxxxxxxx]
894 * tail is nParagraph - nParam1
896 * 3.
897 * ... nParam1 ... nParam2 ... nParagraph ...
898 * [xxxxxxxxxxx]___________|____________
899 * becomes
900 * ___________|____________[xxxxxxxxxxx]
902 * tail is nParam2 - nParam1
905 // sort nParagraph, nParam1 and nParam2 in ascending order, calc range
906 if( nMiddle < nFirst )
908 ::std::swap(nFirst, nMiddle);
910 else if( nMiddle < nLast )
912 nLast = nLast + nMiddle - nFirst;
914 else
916 ::std::swap(nMiddle, nLast);
917 nLast = nLast + nMiddle - nFirst;
920 if( !(nFirst < nParas && nMiddle < nParas && nLast < nParas) )
921 return;
923 // since we have no "paragraph index
924 // changed" event on UAA, remove
925 // [first,last] and insert again later (in
926 // UpdateVisibleChildren)
928 // maParaManager.Rotate( nFirst, nMiddle, nLast );
930 // send CHILD_EVENT to affected children
931 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
932 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
934 ::std::advance( begin, nFirst );
935 ::std::advance( end, nLast+1 );
937 // TODO: maybe optimize here in the following way. If the
938 // number of removed children exceeds a certain threshold,
939 // use InvalidateFlags::Children
940 AccessibleTextHelper_LostChildEvent aFunctor( *this );
942 ::std::for_each( begin, end, aFunctor );
944 maParaManager.Release(nFirst, nLast+1);
945 // should be no need for UpdateBoundRect, since all affected children are cleared.
948 namespace {
950 // functor for sending child events (no stand-alone function, they are maybe not inlined)
951 class AccessibleTextHelper_ChildrenTextChanged
953 public:
954 void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
956 rPara.TextChanged();
960 /** functor processing queue events
962 Reacts on SfxHintId::TextParaInserted/REMOVED events and stores
963 their content
965 class AccessibleTextHelper_QueueFunctor
967 public:
968 AccessibleTextHelper_QueueFunctor() :
969 mnParasChanged( 0 ),
970 mnParaIndex(-1),
971 mnHintId(SfxHintId::NONE)
973 void operator()( const SfxHint* pEvent )
975 if( !pEvent || mnParasChanged == -1 )
976 return;
978 // determine hint type
979 const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent );
980 const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent );
982 if( !(!pEditSourceHint && pTextHint &&
983 (pTextHint->GetId() == SfxHintId::TextParaInserted ||
984 pTextHint->GetId() == SfxHintId::TextParaRemoved )) )
985 return;
987 if( pTextHint->GetValue() == EE_PARA_ALL )
989 mnParasChanged = -1;
991 else
993 mnHintId = pTextHint->GetId();
994 mnParaIndex = pTextHint->GetValue();
995 ++mnParasChanged;
999 /** Query number of paragraphs changed during queue processing.
1001 @return number of changed paragraphs, -1 for
1002 "every paragraph changed"
1004 sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; }
1005 /** Query index of last added/removed paragraph
1007 @return index of lastly added paragraphs, -1 for none
1008 added so far.
1010 sal_Int32 GetParaIndex() const { return mnParaIndex; }
1011 /** Query hint id of last interesting event
1013 @return hint id of last interesting event (REMOVED/INSERTED).
1015 SfxHintId GetHintId() const { return mnHintId; }
1017 private:
1018 /** number of paragraphs changed during queue processing. -1 for
1019 "every paragraph changed"
1021 sal_Int32 mnParasChanged;
1022 /// index of paragraph added/removed last
1023 sal_Int32 mnParaIndex;
1024 /// TextHint ID (removed/inserted) of last interesting event
1025 SfxHintId mnHintId;
1030 void AccessibleTextHelper_Impl::ProcessQueue()
1032 // inspect queue for paragraph insert/remove events. If there
1033 // is exactly _one_ of those in the queue, and the number of
1034 // paragraphs has changed by exactly one, use that event to
1035 // determine a priori which paragraph was added/removed. This
1036 // is necessary, since I must sync right here with the
1037 // EditEngine state (number of paragraphs etc.), since I'm
1038 // potentially sending listener events right away.
1039 AccessibleTextHelper_QueueFunctor aFunctor;
1040 maEventQueue.ForEach( aFunctor );
1042 const sal_Int32 nNewParas( GetTextForwarder().GetParagraphCount() );
1043 const sal_Int32 nCurrParas( maParaManager.GetNum() );
1045 // whether every paragraph already is updated (no need to
1046 // repeat that later on, e.g. for PARA_MOVED events)
1047 bool bEverythingUpdated( false );
1049 if( std::abs( nNewParas - nCurrParas ) == 1 &&
1050 aFunctor.GetNumberOfParasChanged() == 1 )
1052 // #103483# Exactly one paragraph added/removed. This is
1053 // the normal case, optimize event handling here.
1055 if( aFunctor.GetHintId() == SfxHintId::TextParaInserted )
1057 // update num of paras
1058 maParaManager.SetNum( nNewParas );
1060 // release everything from the insertion position until the end
1061 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1063 // TODO: Clarify whether this behaviour _really_ saves
1064 // anybody anything!
1065 // update children, _don't_ broadcast
1066 UpdateVisibleChildren( false );
1067 UpdateBoundRect();
1069 // send insert event
1070 // #109864# Enforce creation of this paragraph
1073 GotPropertyEvent( uno::makeAny( getAccessibleChild( aFunctor.GetParaIndex() -
1074 mnFirstVisibleChild + GetStartIndex() ) ),
1075 AccessibleEventId::CHILD );
1077 catch( const uno::Exception& )
1079 OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph");
1082 else if( aFunctor.GetHintId() == SfxHintId::TextParaRemoved )
1084 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
1085 ::std::advance( begin, aFunctor.GetParaIndex() );
1086 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
1087 ::std::advance( end, 1 );
1089 // #i61812# remember para to be removed for later notification
1090 // AFTER the new state is applied (that after the para got removed)
1091 ::uno::Reference< XAccessible > xPara(begin->first.get());
1093 // release everything from the remove position until the end
1094 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1096 // update num of paras
1097 maParaManager.SetNum( nNewParas );
1099 // TODO: Clarify whether this behaviour _really_ saves
1100 // anybody anything!
1101 // update children, _don't_ broadcast
1102 UpdateVisibleChildren( false );
1103 UpdateBoundRect();
1105 // #i61812# notification for removed para
1106 if (xPara.is())
1107 FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny( xPara) );
1109 #ifdef DBG_UTIL
1110 else
1111 OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue() invalid hint id");
1112 #endif
1114 else if( nNewParas != nCurrParas )
1116 // release all paras
1117 maParaManager.Release(0, nCurrParas);
1119 // update num of paras
1120 maParaManager.SetNum( nNewParas );
1122 // #109864# create from scratch, don't broadcast
1123 UpdateVisibleChildren( false );
1124 UpdateBoundRect();
1126 // number of paragraphs somehow changed - but we have no
1127 // chance determining how. Thus, throw away everything and
1128 // create from scratch.
1129 // (child events should be broadcast after the changes are done...)
1130 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
1132 // no need for further updates later on
1133 bEverythingUpdated = true;
1136 while( !maEventQueue.IsEmpty() )
1138 ::std::unique_ptr< SfxHint > pHint( maEventQueue.PopFront() );
1139 if (pHint)
1141 const SfxHint& rHint = *pHint;
1143 // Note, if you add events here, you need to update the AccessibleTextEventQueue::Append
1144 // code, because only the events we process here, are actually queued there.
1149 if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1151 const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1153 switch( pSdrHint->GetKind() )
1155 case SdrHintKind::BeginEdit:
1157 if(!IsActive())
1159 break;
1161 // change children state
1162 maParaManager.SetActive();
1164 // per definition, edit mode text has the focus
1165 SetFocus( true );
1166 break;
1169 case SdrHintKind::EndEdit:
1171 // focused child now loses focus
1172 ESelection aSelection;
1173 if( GetEditViewForwarder().GetSelection( aSelection ) )
1174 SetChildFocus( aSelection.nEndPara, false );
1176 // change children state
1177 maParaManager.SetActive( false );
1179 maLastSelection = ESelection( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND,
1180 EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND);
1181 break;
1183 default:
1184 break;
1187 else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1189 switch( pEditSourceHint->GetId() )
1191 case SfxHintId::EditSourceParasMoved:
1193 DBG_ASSERT( pEditSourceHint->GetStartValue() < GetTextForwarder().GetParagraphCount() &&
1194 pEditSourceHint->GetEndValue() < GetTextForwarder().GetParagraphCount(),
1195 "AccessibleTextHelper_Impl::NotifyHdl: Invalid notification");
1197 if( !bEverythingUpdated )
1199 ParagraphsMoved(pEditSourceHint->GetStartValue(),
1200 pEditSourceHint->GetValue(),
1201 pEditSourceHint->GetEndValue());
1203 // in all cases, check visibility afterwards.
1204 UpdateVisibleChildren();
1206 break;
1209 case SfxHintId::EditSourceSelectionChanged:
1210 // notify listeners
1213 UpdateSelection();
1215 // maybe we're not in edit mode (this is not an error)
1216 catch( const uno::Exception& ) {}
1217 break;
1218 default: break;
1221 else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1223 const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
1225 switch( pTextHint->GetId() )
1227 case SfxHintId::TextModified:
1229 // notify listeners
1230 sal_Int32 nPara( pTextHint->GetValue() );
1232 // #108900# Delegate change event to children
1233 AccessibleTextHelper_ChildrenTextChanged aNotifyChildrenFunctor;
1235 if( nPara == EE_PARA_ALL )
1237 // #108900# Call every child
1238 ::std::for_each( maParaManager.begin(), maParaManager.end(),
1239 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1241 else
1242 if( nPara < nParas )
1244 // #108900# Call child at index nPara
1245 ::std::for_each( maParaManager.begin()+nPara, maParaManager.begin()+nPara+1,
1246 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1248 break;
1251 case SfxHintId::TextParaInserted:
1252 // already happened above
1253 break;
1255 case SfxHintId::TextParaRemoved:
1256 // already happened above
1257 break;
1259 case SfxHintId::TextHeightChanged:
1260 // visibility changed, done below
1261 break;
1263 case SfxHintId::TextViewScrolled:
1264 // visibility changed, done below
1265 break;
1266 default: break;
1269 // in all cases, check visibility afterwards.
1270 UpdateVisibleChildren();
1271 UpdateBoundRect();
1273 else if ( dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1275 // just check visibility
1276 UpdateVisibleChildren();
1277 UpdateBoundRect();
1279 // it's VITAL to keep the SfxSimpleHint last! It's the base of some classes above!
1280 else if( rHint.GetId() == SfxHintId::Dying)
1282 // edit source is dying under us, become defunc then
1285 // make edit source inaccessible
1286 // Note: cannot destroy it here, since we're called from there!
1287 ShutdownEditSource();
1289 catch( const uno::Exception& ) {}
1292 catch( const uno::Exception& )
1294 DBG_UNHANDLED_EXCEPTION("svx");
1300 void AccessibleTextHelper_Impl::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
1302 // precondition: solar mutex locked
1303 DBG_TESTSOLARMUTEX();
1305 // precondition: not in a recursion
1306 if( mbInNotify )
1307 return;
1309 mbInNotify = true;
1313 // Process notification event, arranged in order of likelihood of
1314 // occurrence to avoid unnecessary dynamic_cast. Note that
1315 // SvxEditSourceHint is derived from TextHint, so has to be checked
1316 // before that.
1317 if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1319 const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1320 // process drawing layer events right away, if not
1321 // within an open EE notification frame. Otherwise,
1322 // event processing would be delayed until next EE
1323 // notification sequence.
1324 maEventQueue.Append( *pSdrHint );
1326 else if( const SvxViewChangedHint* pViewHint = dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1328 // process visibility right away, if not within an
1329 // open EE notification frame. Otherwise, event
1330 // processing would be delayed until next EE
1331 // notification sequence.
1332 maEventQueue.Append( *pViewHint );
1334 else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1336 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1337 maEventQueue.Append( *pEditSourceHint );
1339 else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1341 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1342 if(pTextHint->GetId() == SfxHintId::TextProcessNotifications)
1343 ProcessQueue();
1344 else
1345 maEventQueue.Append( *pTextHint );
1347 // it's VITAL to keep the SfxHint last! It's the base of the classes above!
1348 else if( rHint.GetId() == SfxHintId::Dying )
1350 // handle this event _at once_, because after that, objects are invalid
1351 // edit source is dying under us, become defunc then
1352 maEventQueue.Clear();
1355 // make edit source inaccessible
1356 // Note: cannot destroy it here, since we're called from there!
1357 ShutdownEditSource();
1359 catch( const uno::Exception& ) {}
1362 catch( const uno::Exception& )
1364 DBG_UNHANDLED_EXCEPTION("svx");
1365 mbInNotify = false;
1368 mbInNotify = false;
1371 void AccessibleTextHelper_Impl::Dispose()
1373 if( getNotifierClientId() != -1 )
1377 // #106234# Unregister from EventNotifier
1378 ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() );
1379 SAL_INFO("svx", "disposed ID: " << mnNotifierClientId );
1381 catch( const uno::Exception& ) {}
1383 mnNotifierClientId = -1;
1388 // dispose children
1389 maParaManager.Dispose();
1391 catch( const uno::Exception& ) {}
1393 // quit listen on stale edit source
1394 if( maEditSource.IsValid() )
1395 EndListening( maEditSource.GetBroadcaster() );
1397 // clear references
1398 maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
1399 mxFrontEnd = nullptr;
1402 void AccessibleTextHelper_Impl::FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const
1404 // -- object locked --
1405 AccessibleEventObject aEvent;
1407 osl::MutexGuard aGuard(maMutex);
1409 DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper::FireEvent: no event source set");
1411 if (mxFrontEnd.is())
1412 aEvent = AccessibleEventObject(mxFrontEnd->getAccessibleContext(), nEventId,
1413 rNewValue, rOldValue);
1414 else
1415 aEvent = AccessibleEventObject(uno::Reference<uno::XInterface>(), nEventId,
1416 rNewValue, rOldValue);
1418 // no locking necessary, FireEvent internally copies listeners
1419 // if someone removes/adds in between Further locking,
1420 // actually, might lead to deadlocks, since we're calling out
1421 // of this object
1423 // -- until here --
1425 FireEvent(aEvent);
1428 void AccessibleTextHelper_Impl::FireEvent( const AccessibleEventObject& rEvent ) const
1430 // #102261# Call global queue for focus events
1431 if( rEvent.EventId == AccessibleStateType::FOCUSED )
1432 vcl::unohelper::NotifyAccessibleStateEventGlobally( rEvent );
1434 // #106234# Delegate to EventNotifier
1435 ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(),
1436 rEvent );
1439 // XAccessibleContext
1440 sal_Int32 AccessibleTextHelper_Impl::getAccessibleChildCount() const
1442 return mnLastVisibleChild - mnFirstVisibleChild + 1;
1445 uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int32 i )
1447 i -= GetStartIndex();
1449 if( 0 > i || i >= getAccessibleChildCount() ||
1450 GetTextForwarder().GetParagraphCount() <= i )
1452 throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd);
1455 DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set");
1457 if( mxFrontEnd.is() )
1458 return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first;
1459 else
1460 return nullptr;
1463 void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1465 if( getNotifierClientId() != -1 )
1466 ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener );
1469 void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1471 if( getNotifierClientId() == -1 )
1472 return;
1474 const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener );
1475 if ( !nListenerCount )
1477 // no listeners anymore
1478 // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client),
1479 // and at least to us not firing any events anymore, in case somebody calls
1480 // NotifyAccessibleEvent, again
1481 ::comphelper::AccessibleEventNotifier::TClientId nId( getNotifierClientId() );
1482 mnNotifierClientId = -1;
1483 ::comphelper::AccessibleEventNotifier::revokeClient( nId );
1487 uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint )
1489 // make given position relative
1490 if( !mxFrontEnd.is() )
1491 throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1493 uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext();
1495 if( !xFrontEndContext.is() )
1496 throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1498 uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW );
1500 // #103862# No longer need to make given position relative
1501 Point aPoint( _aPoint.X, _aPoint.Y );
1503 // respect EditEngine offset to surrounding shape/cell
1504 aPoint -= GetOffset();
1506 // convert to EditEngine coordinate system
1507 SvxTextForwarder& rCacheTF = GetTextForwarder();
1508 Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) );
1510 // iterate over all visible children (including those not yet created)
1511 sal_Int32 nChild;
1512 for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild )
1514 DBG_ASSERT(nChild >= 0,
1515 "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow");
1517 tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) );
1519 if( aParaBounds.IsInside( aLogPoint ) )
1520 return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() );
1523 // found none
1524 return nullptr;
1528 // AccessibleTextHelper implementation (simply forwards to impl)
1530 AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) :
1531 mpImpl( new AccessibleTextHelper_Impl() )
1533 SolarMutexGuard aGuard;
1535 SetEditSource( std::move(pEditSource) );
1538 AccessibleTextHelper::~AccessibleTextHelper()
1542 const SvxEditSource& AccessibleTextHelper::GetEditSource() const
1544 #ifdef DBG_UTIL
1545 mpImpl->CheckInvariants();
1547 const SvxEditSource& aEditSource = mpImpl->GetEditSource();
1549 mpImpl->CheckInvariants();
1551 return aEditSource;
1552 #else
1553 return mpImpl->GetEditSource();
1554 #endif
1557 void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
1559 #ifdef DBG_UTIL
1560 // precondition: solar mutex locked
1561 DBG_TESTSOLARMUTEX();
1563 mpImpl->CheckInvariants();
1564 #endif
1566 mpImpl->SetEditSource( std::move(pEditSource) );
1568 #ifdef DBG_UTIL
1569 mpImpl->CheckInvariants();
1570 #endif
1573 void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface )
1575 #ifdef DBG_UTIL
1576 mpImpl->CheckInvariants();
1577 #endif
1579 mpImpl->SetEventSource( rInterface );
1581 #ifdef DBG_UTIL
1582 mpImpl->CheckInvariants();
1583 #endif
1586 void AccessibleTextHelper::SetFocus( bool bHaveFocus )
1588 #ifdef DBG_UTIL
1589 // precondition: solar mutex locked
1590 DBG_TESTSOLARMUTEX();
1592 mpImpl->CheckInvariants();
1593 #endif
1595 mpImpl->SetFocus( bHaveFocus );
1597 #ifdef DBG_UTIL
1598 mpImpl->CheckInvariants();
1599 #endif
1602 bool AccessibleTextHelper::HaveFocus()
1604 #ifdef DBG_UTIL
1605 mpImpl->CheckInvariants();
1607 bool bRet( mpImpl->HaveFocus() );
1609 mpImpl->CheckInvariants();
1611 return bRet;
1612 #else
1613 return mpImpl->HaveFocus();
1614 #endif
1617 void AccessibleTextHelper::SetOffset( const Point& rPoint )
1619 #ifdef DBG_UTIL
1620 // precondition: solar mutex locked
1621 DBG_TESTSOLARMUTEX();
1623 mpImpl->CheckInvariants();
1624 #endif
1626 mpImpl->SetOffset( rPoint );
1628 #ifdef DBG_UTIL
1629 mpImpl->CheckInvariants();
1630 #endif
1633 void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset )
1635 #ifdef DBG_UTIL
1636 // precondition: solar mutex locked
1637 DBG_TESTSOLARMUTEX();
1639 mpImpl->CheckInvariants();
1640 #endif
1642 mpImpl->SetStartIndex( nOffset );
1644 #ifdef DBG_UTIL
1645 mpImpl->CheckInvariants();
1646 #endif
1649 sal_Int32 AccessibleTextHelper::GetStartIndex() const
1651 #ifdef DBG_UTIL
1652 mpImpl->CheckInvariants();
1654 sal_Int32 nOffset = mpImpl->GetStartIndex();
1656 mpImpl->CheckInvariants();
1658 return nOffset;
1659 #else
1660 return mpImpl->GetStartIndex();
1661 #endif
1664 void AccessibleTextHelper::SetAdditionalChildStates( const VectorOfStates& rChildStates )
1666 mpImpl->SetAdditionalChildStates( rChildStates );
1669 void AccessibleTextHelper::UpdateChildren()
1671 #ifdef DBG_UTIL
1672 // precondition: solar mutex locked
1673 DBG_TESTSOLARMUTEX();
1675 mpImpl->CheckInvariants();
1676 #endif
1678 mpImpl->UpdateVisibleChildren();
1679 mpImpl->UpdateBoundRect();
1681 mpImpl->UpdateSelection();
1683 #ifdef DBG_UTIL
1684 mpImpl->CheckInvariants();
1685 #endif
1688 void AccessibleTextHelper::Dispose()
1690 // As Dispose calls ShutdownEditSource, which in turn
1691 // deregisters as listener on the edit source, have to lock
1692 // here
1693 SolarMutexGuard aGuard;
1695 #ifdef DBG_UTIL
1696 mpImpl->CheckInvariants();
1697 #endif
1699 mpImpl->Dispose();
1701 #ifdef DBG_UTIL
1702 mpImpl->CheckInvariants();
1703 #endif
1706 // XAccessibleContext
1707 sal_Int32 AccessibleTextHelper::GetChildCount() const
1709 SolarMutexGuard aGuard;
1711 #ifdef DBG_UTIL
1712 mpImpl->CheckInvariants();
1714 sal_Int32 nRet = mpImpl->getAccessibleChildCount();
1716 mpImpl->CheckInvariants();
1718 return nRet;
1719 #else
1720 return mpImpl->getAccessibleChildCount();
1721 #endif
1724 uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int32 i )
1726 SolarMutexGuard aGuard;
1728 #ifdef DBG_UTIL
1729 mpImpl->CheckInvariants();
1731 uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i );
1733 mpImpl->CheckInvariants();
1735 return xRet;
1736 #else
1737 return mpImpl->getAccessibleChild( i );
1738 #endif
1741 void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1743 #ifdef DBG_UTIL
1744 mpImpl->CheckInvariants();
1746 mpImpl->addAccessibleEventListener( xListener );
1748 mpImpl->CheckInvariants();
1749 #else
1750 mpImpl->addAccessibleEventListener( xListener );
1751 #endif
1754 void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1756 #ifdef DBG_UTIL
1757 mpImpl->CheckInvariants();
1759 mpImpl->removeAccessibleEventListener( xListener );
1761 mpImpl->CheckInvariants();
1762 #else
1763 mpImpl->removeAccessibleEventListener( xListener );
1764 #endif
1767 // XAccessibleComponent
1768 uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint )
1770 SolarMutexGuard aGuard;
1772 #ifdef DBG_UTIL
1773 mpImpl->CheckInvariants();
1775 uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint );
1777 mpImpl->CheckInvariants();
1779 return xChild;
1780 #else
1781 return mpImpl->getAccessibleAtPoint( aPoint );
1782 #endif
1785 } // end of namespace accessibility
1788 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */