Bump version to 6.4.7.2.M8
[LibreOffice.git] / svx / source / accessibility / AccessibleTextHelper.cxx
blob68a0e40cff9f6ea2ba3a896aa38a7d5841328a54
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 .
21 // Global header
24 #include <memory>
25 #include <utility>
26 #include <algorithm>
27 #include <deque>
28 #include <osl/mutex.hxx>
29 #include <sal/log.hxx>
30 #include <com/sun/star/uno/Any.hxx>
31 #include <com/sun/star/uno/Reference.hxx>
32 #include <cppuhelper/weakref.hxx>
33 #include <com/sun/star/awt/Point.hpp>
34 #include <com/sun/star/awt/Rectangle.hpp>
35 #include <com/sun/star/lang/DisposedException.hpp>
36 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
37 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
38 #include <com/sun/star/accessibility/XAccessible.hpp>
39 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
40 #include <com/sun/star/accessibility/XAccessibleComponent.hpp>
41 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
42 #include <comphelper/accessibleeventnotifier.hxx>
43 #include <unotools/accessiblestatesethelper.hxx>
44 #include <vcl/svapp.hxx>
45 #include <vcl/textdata.hxx>
46 #include <vcl/unohelp.hxx>
47 #include <sfx2/viewfrm.hxx>
48 #include <sfx2/viewsh.hxx>
51 // Project-local header
54 #include "AccessibleTextEventQueue.hxx"
55 #include <svx/AccessibleTextHelper.hxx>
56 #include <svx/unoshape.hxx>
57 #include <editeng/unolingu.hxx>
58 #include <editeng/unotext.hxx>
60 #include <editeng/unoedhlp.hxx>
61 #include <editeng/unopracc.hxx>
62 #include <editeng/unoedprx.hxx>
63 #include <editeng/AccessibleParaManager.hxx>
64 #include <editeng/AccessibleEditableTextPara.hxx>
65 #include <svx/svdmodel.hxx>
66 #include <svx/svdpntv.hxx>
67 #include <cell.hxx>
68 #include "../table/accessiblecell.hxx"
69 #include <editeng/editdata.hxx>
70 #include <editeng/editeng.hxx>
71 #include <editeng/editview.hxx>
72 #include <tools/debug.hxx>
73 #include <tools/diagnose_ex.h>
75 using namespace ::com::sun::star;
76 using namespace ::com::sun::star::accessibility;
78 namespace accessibility
81 // AccessibleTextHelper_Impl declaration
83 template < typename first_type, typename second_type >
84 static ::std::pair< first_type, second_type > makeSortedPair( first_type first,
85 second_type second )
87 if( first > second )
88 return ::std::make_pair( second, first );
89 else
90 return ::std::make_pair( first, second );
93 class AccessibleTextHelper_Impl : public SfxListener
95 public:
96 typedef ::std::vector< sal_Int16 > VectorOfStates;
98 // receive pointer to our frontend class and view window
99 AccessibleTextHelper_Impl();
100 virtual ~AccessibleTextHelper_Impl() override;
102 // XAccessibleContext child handling methods
103 sal_Int32 getAccessibleChildCount() const;
104 uno::Reference< XAccessible > getAccessibleChild( sal_Int32 i );
106 // XAccessibleEventBroadcaster child related methods
107 void addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener );
108 void removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener );
110 // XAccessibleComponent child related methods
111 uno::Reference< XAccessible > getAccessibleAtPoint( const awt::Point& aPoint );
113 SvxEditSourceAdapter& GetEditSource() const;
115 void SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource );
117 void SetEventSource( const uno::Reference< XAccessible >& rInterface )
119 mxFrontEnd = rInterface;
122 void SetOffset( const Point& );
123 Point GetOffset() const
125 ::osl::MutexGuard aGuard( maMutex ); Point aPoint( maOffset );
126 return aPoint;
129 void SetStartIndex( sal_Int32 nOffset );
130 sal_Int32 GetStartIndex() const
132 // Strictly correct only with locked solar mutex, // but
133 // here we rely on the fact that sal_Int32 access is
134 // atomic
135 return mnStartIndex;
138 void SetAdditionalChildStates( const VectorOfStates& rChildStates );
140 void Dispose();
142 // do NOT hold object mutex when calling this! Danger of deadlock
143 void FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue = uno::Any(), const uno::Any& rOldValue = uno::Any() ) const;
144 void FireEvent( const AccessibleEventObject& rEvent ) const;
146 void SetFocus( bool bHaveFocus );
147 bool HaveFocus()
149 // No locking of solar mutex here, since we rely on the fact
150 // that sal_Bool access is atomic
151 return mbThisHasFocus;
153 void SetChildFocus( sal_Int32 nChild, bool bHaveFocus );
154 void SetShapeFocus( bool bHaveFocus );
155 void ChangeChildFocus( sal_Int32 nNewChild );
157 #ifdef DBG_UTIL
158 void CheckInvariants() const;
159 #endif
161 // checks all children for visibility, throws away invisible ones
162 void UpdateVisibleChildren( bool bBroadcastEvents=true );
164 // check all children for changes in position and size
165 void UpdateBoundRect();
167 // calls SetSelection on the forwarder and updates maLastSelection
168 // cache.
169 void UpdateSelection();
171 private:
173 // Process event queue
174 void ProcessQueue();
176 // syntactic sugar for FireEvent
177 void GotPropertyEvent( const uno::Any& rNewValue, const sal_Int16 nEventId ) const { FireEvent( nEventId, rNewValue ); }
179 // shutdown usage of current edit source on myself and the children.
180 void ShutdownEditSource();
182 void ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast );
184 virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
186 int getNotifierClientId() const { return mnNotifierClientId; }
188 // lock solar mutex before
189 SvxTextForwarder& GetTextForwarder() const;
190 // lock solar mutex before
191 SvxViewForwarder& GetViewForwarder() const;
192 // lock solar mutex before
193 SvxEditViewForwarder& GetEditViewForwarder() const;
195 // are we in edit mode?
196 bool IsActive() const;
198 // our frontend class (the one implementing the actual
199 // interface). That's not necessarily the one containing the impl
200 // pointer!
201 uno::Reference< XAccessible > mxFrontEnd;
203 // a wrapper for the text forwarders (guarded by solar mutex)
204 mutable SvxEditSourceAdapter maEditSource;
206 // store last selection (to correctly report selection changes, guarded by solar mutex)
207 ESelection maLastSelection;
209 // cache range of visible children (guarded by solar mutex)
210 sal_Int32 mnFirstVisibleChild;
211 sal_Int32 mnLastVisibleChild;
213 // offset to add to all our children (unguarded, relying on
214 // the fact that sal_Int32 access is atomic)
215 sal_Int32 mnStartIndex;
217 // the object handling our children (guarded by solar mutex)
218 ::accessibility::AccessibleParaManager maParaManager;
220 // Queued events from Notify() (guarded by solar mutex)
221 AccessibleTextEventQueue maEventQueue;
223 // spin lock to prevent notify in notify (guarded by solar mutex)
224 bool mbInNotify;
226 // whether the object or its children has the focus set (guarded by solar mutex)
227 bool mbGroupHasFocus;
229 // whether we (this object) has the focus set (guarded by solar mutex)
230 bool mbThisHasFocus;
232 mutable ::osl::Mutex maMutex;
234 /// our current offset to the containing shape/cell (guarded by maMutex)
235 Point maOffset;
237 /// client Id from AccessibleEventNotifier
238 int mnNotifierClientId;
241 AccessibleTextHelper_Impl::AccessibleTextHelper_Impl() :
242 maLastSelection( EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND,EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND ),
243 mnFirstVisibleChild( -1 ),
244 mnLastVisibleChild( -2 ),
245 mnStartIndex( 0 ),
246 mbInNotify( false ),
247 mbGroupHasFocus( false ),
248 mbThisHasFocus( false ),
249 maOffset(0,0),
250 // well, that's strictly exception safe, though not really
251 // robust. We rely on the fact that this member is constructed
252 // last, and that the constructor body is empty, thus no
253 // chance for exceptions once the Id is fetched. Nevertheless,
254 // normally should employ RAII here...
255 mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient())
257 SAL_INFO("svx", "received ID: " << mnNotifierClientId );
260 AccessibleTextHelper_Impl::~AccessibleTextHelper_Impl()
262 SolarMutexGuard aGuard;
266 // call Dispose here, too, since we've some resources not
267 // automatically freed otherwise
268 Dispose();
270 catch( const uno::Exception& ) {}
273 SvxTextForwarder& AccessibleTextHelper_Impl::GetTextForwarder() const
275 if( !maEditSource.IsValid() )
276 throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
278 SvxTextForwarder* pTextForwarder = maEditSource.GetTextForwarder();
280 if( !pTextForwarder )
281 throw uno::RuntimeException("Unable to fetch text forwarder, model might be dead", mxFrontEnd);
283 if( !pTextForwarder->IsValid() )
284 throw uno::RuntimeException("Text forwarder is invalid, model might be dead", mxFrontEnd);
286 return *pTextForwarder;
289 SvxViewForwarder& AccessibleTextHelper_Impl::GetViewForwarder() const
291 if( !maEditSource.IsValid() )
292 throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
294 SvxViewForwarder* pViewForwarder = maEditSource.GetViewForwarder();
296 if( !pViewForwarder )
297 throw uno::RuntimeException("Unable to fetch view forwarder, model might be dead", mxFrontEnd);
299 if( !pViewForwarder->IsValid() )
300 throw uno::RuntimeException("View forwarder is invalid, model might be dead", mxFrontEnd);
302 return *pViewForwarder;
305 SvxEditViewForwarder& AccessibleTextHelper_Impl::GetEditViewForwarder() const
307 if( !maEditSource.IsValid() )
308 throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
310 SvxEditViewForwarder* pViewForwarder = maEditSource.GetEditViewForwarder();
312 if( !pViewForwarder )
314 throw uno::RuntimeException("No edit view forwarder, object not in edit mode", mxFrontEnd);
317 if( !pViewForwarder->IsValid() )
319 throw uno::RuntimeException("View forwarder is invalid, object not in edit mode", mxFrontEnd);
322 return *pViewForwarder;
325 SvxEditSourceAdapter& AccessibleTextHelper_Impl::GetEditSource() const
327 if( !maEditSource.IsValid() )
328 throw uno::RuntimeException("AccessibleTextHelper_Impl::GetEditSource: no edit source", mxFrontEnd );
329 return maEditSource;
332 // functor for sending child events (no stand-alone function, they are maybe not inlined)
333 class AccessibleTextHelper_OffsetChildIndex
335 public:
336 explicit AccessibleTextHelper_OffsetChildIndex( sal_Int32 nDifference ) : mnDifference(nDifference) {}
337 void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
339 rPara.SetIndexInParent( rPara.GetIndexInParent() + mnDifference );
342 private:
343 const sal_Int32 mnDifference;
346 void AccessibleTextHelper_Impl::SetStartIndex( sal_Int32 nOffset )
348 sal_Int32 nOldOffset( mnStartIndex );
350 mnStartIndex = nOffset;
352 if( nOldOffset != nOffset )
354 // update children
355 AccessibleTextHelper_OffsetChildIndex aFunctor( nOffset - nOldOffset );
357 ::std::for_each( maParaManager.begin(), maParaManager.end(),
358 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_OffsetChildIndex > (aFunctor) );
362 void AccessibleTextHelper_Impl::SetAdditionalChildStates( const VectorOfStates& rChildStates )
364 maParaManager.SetAdditionalChildStates( rChildStates );
367 void AccessibleTextHelper_Impl::SetChildFocus( sal_Int32 nChild, bool bHaveFocus )
369 if( bHaveFocus )
371 if( mbThisHasFocus )
372 SetShapeFocus( false );
374 maParaManager.SetFocus( nChild );
376 // we just received the focus, also send caret event then
377 UpdateSelection();
379 SAL_INFO("svx", "Paragraph " << nChild << " received focus");
381 else
383 maParaManager.SetFocus( -1 );
385 SAL_INFO("svx", "Paragraph " << nChild << " lost focus");
387 if( mbGroupHasFocus )
388 SetShapeFocus( true );
392 void AccessibleTextHelper_Impl::ChangeChildFocus( sal_Int32 nNewChild )
394 if( mbThisHasFocus )
395 SetShapeFocus( false );
397 mbGroupHasFocus = true;
398 maParaManager.SetFocus( nNewChild );
400 SAL_INFO("svx", "Paragraph " << nNewChild << " received focus");
403 void AccessibleTextHelper_Impl::SetShapeFocus( bool bHaveFocus )
405 bool bOldFocus( mbThisHasFocus );
407 mbThisHasFocus = bHaveFocus;
409 if( bOldFocus != bHaveFocus )
411 if( bHaveFocus )
413 if( mxFrontEnd.is() )
415 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
416 if ( !pAccessibleCell )
417 GotPropertyEvent( uno::makeAny(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED );
418 else // the focus event on cell should be fired on table directly
420 AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable();
421 if (pAccTable)
422 pAccTable->SetStateDirectly(AccessibleStateType::FOCUSED);
425 SAL_INFO("svx", "Parent object received focus" );
427 else
429 // The focus state should be reset directly on table.
430 //LostPropertyEvent( uno::makeAny(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED );
431 if( mxFrontEnd.is() )
433 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
434 if ( !pAccessibleCell )
435 FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::makeAny(AccessibleStateType::FOCUSED) );
436 else
438 AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable();
439 if (pAccTable)
440 pAccTable->ResetStateDirectly(AccessibleStateType::FOCUSED);
443 SAL_INFO("svx", "Parent object lost focus" );
448 void AccessibleTextHelper_Impl::SetFocus( bool bHaveFocus )
450 bool bOldFocus( mbGroupHasFocus );
452 mbGroupHasFocus = bHaveFocus;
454 if( IsActive() )
458 // find the one with the cursor and get/set focus accordingly
459 ESelection aSelection;
460 if( GetEditViewForwarder().GetSelection( aSelection ) )
461 SetChildFocus( aSelection.nEndPara, bHaveFocus );
463 catch( const uno::Exception& ) {}
465 else if( bOldFocus != bHaveFocus )
467 SetShapeFocus( bHaveFocus );
470 SAL_INFO("svx", "focus changed, Object " << this << ", state: " << (bHaveFocus ? "focused" : "not focused") );
473 bool AccessibleTextHelper_Impl::IsActive() const
477 SvxEditSource& rEditSource = GetEditSource();
478 SvxEditViewForwarder* pViewForwarder = rEditSource.GetEditViewForwarder();
480 if( !pViewForwarder )
481 return false;
483 if( mxFrontEnd.is() )
485 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
486 if ( pAccessibleCell )
488 sdr::table::CellRef xCell = pAccessibleCell->getCellRef();
489 if ( xCell.is() )
490 return xCell->IsActiveCell();
493 return pViewForwarder->IsValid();
495 catch( const uno::RuntimeException& )
497 return false;
501 void AccessibleTextHelper_Impl::UpdateSelection()
505 ESelection aSelection;
506 if( GetEditViewForwarder().GetSelection( aSelection ) )
508 if( maLastSelection != aSelection &&
509 aSelection.nEndPara < maParaManager.GetNum() )
511 // #103998# Not that important, changed from assertion to trace
512 if( mbThisHasFocus )
514 SAL_INFO("svx", "Parent has focus!");
517 sal_Int32 nMaxValidParaIndex( GetTextForwarder().GetParagraphCount() - 1 );
519 // notify all affected paragraphs (TODO: may be suboptimal,
520 // since some paragraphs might stay selected)
521 if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND )
523 // Did the caret move from one paragraph to another?
524 // #100530# no caret events if not focused.
525 if( mbGroupHasFocus &&
526 maLastSelection.nEndPara != aSelection.nEndPara )
528 if( maLastSelection.nEndPara < maParaManager.GetNum() )
530 maParaManager.FireEvent( ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ),
531 ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex )+1,
532 AccessibleEventId::CARET_CHANGED,
533 uno::makeAny(static_cast<sal_Int32>(-1)),
534 uno::makeAny(maLastSelection.nEndPos) );
537 ChangeChildFocus( aSelection.nEndPara );
539 SAL_INFO(
540 "svx",
541 "focus changed, Object: " << this
542 << ", Paragraph: " << aSelection.nEndPara
543 << ", Last paragraph: "
544 << maLastSelection.nEndPara);
548 // #100530# no caret events if not focused.
549 if( mbGroupHasFocus )
551 uno::Any aOldCursor;
553 // #i13705# The old cursor can only contain valid
554 // values if it's the same paragraph!
555 if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND &&
556 maLastSelection.nEndPara == aSelection.nEndPara )
558 aOldCursor <<= maLastSelection.nEndPos;
560 else
562 aOldCursor <<= static_cast<sal_Int32>(-1);
565 maParaManager.FireEvent( aSelection.nEndPara,
566 aSelection.nEndPara+1,
567 AccessibleEventId::CARET_CHANGED,
568 uno::makeAny(aSelection.nEndPos),
569 aOldCursor );
572 SAL_INFO(
573 "svx",
574 "caret changed, Object: " << this << ", New pos: "
575 << aSelection.nEndPos << ", Old pos: "
576 << maLastSelection.nEndPos << ", New para: "
577 << aSelection.nEndPara << ", Old para: "
578 << maLastSelection.nEndPara);
580 // #108947# Sort new range before calling FireEvent
581 ::std::pair<sal_Int32, sal_Int32> sortedSelection(
582 makeSortedPair(::std::min( aSelection.nStartPara, nMaxValidParaIndex ),
583 ::std::min( aSelection.nEndPara, nMaxValidParaIndex ) ) );
585 // #108947# Sort last range before calling FireEvent
586 ::std::pair<sal_Int32, sal_Int32> sortedLastSelection(
587 makeSortedPair(::std::min( maLastSelection.nStartPara, nMaxValidParaIndex ),
588 ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ) ) );
590 // event TEXT_SELECTION_CHANGED has to be submitted. (#i27299#)
591 const sal_Int16 nTextSelChgEventId =
592 AccessibleEventId::TEXT_SELECTION_CHANGED;
593 // #107037# notify selection change
594 if( maLastSelection.nStartPara == EE_PARA_NOT_FOUND )
596 // last selection is undefined
597 // use method <ESelection::HasRange()> (#i27299#)
598 if ( aSelection.HasRange() )
600 // selection was undefined, now is on
601 maParaManager.FireEvent( sortedSelection.first,
602 sortedSelection.second+1,
603 nTextSelChgEventId );
606 else
608 // last selection is valid
609 // use method <ESelection::HasRange()> (#i27299#)
610 if ( maLastSelection.HasRange() &&
611 !aSelection.HasRange() )
613 // selection was on, now is empty
614 maParaManager.FireEvent( sortedLastSelection.first,
615 sortedLastSelection.second+1,
616 nTextSelChgEventId );
618 // use method <ESelection::HasRange()> (#i27299#)
619 else if( !maLastSelection.HasRange() &&
620 aSelection.HasRange() )
622 // selection was empty, now is on
623 maParaManager.FireEvent( sortedSelection.first,
624 sortedSelection.second+1,
625 nTextSelChgEventId );
627 // no event TEXT_SELECTION_CHANGED event, if new and
628 // last selection are empty. (#i27299#)
629 else if ( maLastSelection.HasRange() &&
630 aSelection.HasRange() )
632 // use sorted last and new selection
633 ESelection aTmpLastSel( maLastSelection );
634 aTmpLastSel.Adjust();
635 ESelection aTmpSel( aSelection );
636 aTmpSel.Adjust();
637 // first submit event for new and changed selection
638 sal_Int32 nPara = aTmpSel.nStartPara;
639 for ( ; nPara <= aTmpSel.nEndPara; ++nPara )
641 if ( nPara < aTmpLastSel.nStartPara ||
642 nPara > aTmpLastSel.nEndPara )
644 // new selection on paragraph <nPara>
645 maParaManager.FireEvent( nPara,
646 nTextSelChgEventId );
648 else
650 // check for changed selection on paragraph <nPara>
651 const sal_Int32 nParaStartPos =
652 nPara == aTmpSel.nStartPara
653 ? aTmpSel.nStartPos : 0;
654 const sal_Int32 nParaEndPos =
655 nPara == aTmpSel.nEndPara
656 ? aTmpSel.nEndPos : -1;
657 const sal_Int32 nLastParaStartPos =
658 nPara == aTmpLastSel.nStartPara
659 ? aTmpLastSel.nStartPos : 0;
660 const sal_Int32 nLastParaEndPos =
661 nPara == aTmpLastSel.nEndPara
662 ? aTmpLastSel.nEndPos : -1;
663 if ( nParaStartPos != nLastParaStartPos ||
664 nParaEndPos != nLastParaEndPos )
666 maParaManager.FireEvent(
667 nPara, nTextSelChgEventId );
671 // second submit event for 'old' selections
672 nPara = aTmpLastSel.nStartPara;
673 for ( ; nPara <= aTmpLastSel.nEndPara; ++nPara )
675 if ( nPara < aTmpSel.nStartPara ||
676 nPara > aTmpSel.nEndPara )
678 maParaManager.FireEvent( nPara,
679 nTextSelChgEventId );
685 maLastSelection = aSelection;
689 // no selection? no update actions
690 catch( const uno::RuntimeException& ) {}
693 void AccessibleTextHelper_Impl::ShutdownEditSource()
695 // This should only be called with solar mutex locked, i.e. from the main office thread
697 // This here is somewhat clumsy: As soon as our children have
698 // a NULL EditSource (maParaManager.SetEditSource()), they
699 // enter the disposed state and cannot be reanimated. Thus, it
700 // is unavoidable and a hard requirement to let go and create
701 // from scratch each and every child.
703 // invalidate children
704 maParaManager.Dispose();
705 maParaManager.SetNum(0);
707 // lost all children
708 if( mxFrontEnd.is() )
709 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
711 // quit listen on stale edit source
712 if( maEditSource.IsValid() )
713 EndListening( maEditSource.GetBroadcaster() );
715 maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
718 void AccessibleTextHelper_Impl::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
720 // This should only be called with solar mutex locked, i.e. from the main office thread
722 // shutdown old edit source
723 ShutdownEditSource();
725 // set new edit source
726 maEditSource.SetEditSource( std::move(pEditSource) );
728 // init child vector to the current child count
729 if( maEditSource.IsValid() )
731 maParaManager.SetNum( GetTextForwarder().GetParagraphCount() );
733 // listen on new edit source
734 StartListening( maEditSource.GetBroadcaster() );
736 UpdateVisibleChildren();
740 void AccessibleTextHelper_Impl::SetOffset( const Point& rPoint )
742 // guard against non-atomic access to maOffset data structure
744 ::osl::MutexGuard aGuard( maMutex );
745 maOffset = rPoint;
748 maParaManager.SetEEOffset( rPoint );
750 // in all cases, check visibility afterwards.
751 UpdateVisibleChildren();
752 UpdateBoundRect();
755 void AccessibleTextHelper_Impl::UpdateVisibleChildren( bool bBroadcastEvents )
759 SvxTextForwarder& rCacheTF = GetTextForwarder();
760 sal_Int32 nParas=rCacheTF.GetParagraphCount();
762 mnFirstVisibleChild = -1;
763 mnLastVisibleChild = -2;
765 for( sal_Int32 nCurrPara=0; nCurrPara<nParas; ++nCurrPara )
767 if (nCurrPara == 0)
768 mnFirstVisibleChild = nCurrPara;
769 mnLastVisibleChild = nCurrPara;
770 if (mxFrontEnd.is() && bBroadcastEvents)
772 // child not yet created?
773 ::accessibility::AccessibleParaManager::WeakChild aChild( maParaManager.GetChild(nCurrPara) );
774 if( aChild.second.Width == 0 &&
775 aChild.second.Height == 0 )
777 GotPropertyEvent( uno::makeAny( maParaManager.CreateChild( nCurrPara - mnFirstVisibleChild,
778 mxFrontEnd, GetEditSource(), nCurrPara ).first ),
779 AccessibleEventId::CHILD );
784 catch( const uno::Exception& )
786 OSL_FAIL("AccessibleTextHelper_Impl::UpdateVisibleChildren error while determining visible children");
788 // something failed - currently no children
789 mnFirstVisibleChild = -1;
790 mnLastVisibleChild = -2;
791 maParaManager.SetNum(0);
793 // lost all children
794 if( bBroadcastEvents )
795 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
799 // functor for checking changes in paragraph bounding boxes (no stand-alone function, maybe not inlined)
800 class AccessibleTextHelper_UpdateChildBounds
802 public:
803 explicit AccessibleTextHelper_UpdateChildBounds() {}
804 ::accessibility::AccessibleParaManager::WeakChild operator()( const ::accessibility::AccessibleParaManager::WeakChild& rChild )
806 // retrieve hard reference from weak one
807 auto aHardRef( rChild.first.get() );
809 if( aHardRef.is() )
811 awt::Rectangle aNewRect = aHardRef->getBounds();
812 const awt::Rectangle& aOldRect = rChild.second;
814 if( aNewRect.X != aOldRect.X ||
815 aNewRect.Y != aOldRect.Y ||
816 aNewRect.Width != aOldRect.Width ||
817 aNewRect.Height != aOldRect.Height )
819 // visible data changed
820 aHardRef->FireEvent( AccessibleEventId::BOUNDRECT_CHANGED );
822 // update internal bounds
823 return ::accessibility::AccessibleParaManager::WeakChild( rChild.first, aNewRect );
827 // identity transform
828 return rChild;
832 void AccessibleTextHelper_Impl::UpdateBoundRect()
834 // send BOUNDRECT_CHANGED to affected children
835 AccessibleTextHelper_UpdateChildBounds aFunctor;
836 ::std::transform( maParaManager.begin(), maParaManager.end(), maParaManager.begin(), aFunctor );
839 #ifdef DBG_UTIL
840 void AccessibleTextHelper_Impl::CheckInvariants() const
842 if( mnFirstVisibleChild >= 0 &&
843 mnFirstVisibleChild > mnLastVisibleChild )
845 OSL_FAIL( "AccessibleTextHelper: range invalid" );
848 #endif
850 // functor for sending child events (no stand-alone function, they are maybe not inlined)
851 class AccessibleTextHelper_LostChildEvent
853 public:
854 explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {}
855 void operator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara )
857 // retrieve hard reference from weak one
858 auto aHardRef( rPara.first.get() );
860 if( aHardRef.is() )
861 mrImpl.FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny<css::uno::Reference<css::accessibility::XAccessible>>(aHardRef.get()) );
864 private:
865 AccessibleTextHelper_Impl& mrImpl;
868 void AccessibleTextHelper_Impl::ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast )
870 const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
872 /* rotate paragraphs
873 * =================
875 * Three cases:
877 * 1.
878 * ... nParagraph ... nParam1 ... nParam2 ...
879 * |______________[xxxxxxxxxxx]
880 * becomes
881 * [xxxxxxxxxxx]|______________
883 * tail is 0
885 * 2.
886 * ... nParam1 ... nParagraph ... nParam2 ...
887 * [xxxxxxxxxxx|xxxxxxxxxxxxxx]____________
888 * becomes
889 * ____________[xxxxxxxxxxx|xxxxxxxxxxxxxx]
891 * tail is nParagraph - nParam1
893 * 3.
894 * ... nParam1 ... nParam2 ... nParagraph ...
895 * [xxxxxxxxxxx]___________|____________
896 * becomes
897 * ___________|____________[xxxxxxxxxxx]
899 * tail is nParam2 - nParam1
902 // sort nParagraph, nParam1 and nParam2 in ascending order, calc range
903 if( nMiddle < nFirst )
905 ::std::swap(nFirst, nMiddle);
907 else if( nMiddle < nLast )
909 nLast = nLast + nMiddle - nFirst;
911 else
913 ::std::swap(nMiddle, nLast);
914 nLast = nLast + nMiddle - nFirst;
917 if( nFirst < nParas && nMiddle < nParas && nLast < nParas )
919 // since we have no "paragraph index
920 // changed" event on UAA, remove
921 // [first,last] and insert again later (in
922 // UpdateVisibleChildren)
924 // maParaManager.Rotate( nFirst, nMiddle, nLast );
926 // send CHILD_EVENT to affected children
927 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
928 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
930 ::std::advance( begin, nFirst );
931 ::std::advance( end, nLast+1 );
933 // TODO: maybe optimize here in the following way. If the
934 // number of removed children exceeds a certain threshold,
935 // use InvalidateFlags::Children
936 AccessibleTextHelper_LostChildEvent aFunctor( *this );
938 ::std::for_each( begin, end, aFunctor );
940 maParaManager.Release(nFirst, nLast+1);
941 // should be no need for UpdateBoundRect, since all affected children are cleared.
945 // functor for sending child events (no stand-alone function, they are maybe not inlined)
946 class AccessibleTextHelper_ChildrenTextChanged
948 public:
949 void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
951 rPara.TextChanged();
955 /** functor processing queue events
957 Reacts on SfxHintId::TextParaInserted/REMOVED events and stores
958 their content
960 class AccessibleTextHelper_QueueFunctor
962 public:
963 AccessibleTextHelper_QueueFunctor() :
964 mnParasChanged( 0 ),
965 mnParaIndex(-1),
966 mnHintId(SfxHintId::NONE)
968 void operator()( const SfxHint* pEvent )
970 if( pEvent &&
971 mnParasChanged != -1 )
973 // determine hint type
974 const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent );
975 const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent );
977 if( !pEditSourceHint && pTextHint &&
978 (pTextHint->GetId() == SfxHintId::TextParaInserted ||
979 pTextHint->GetId() == SfxHintId::TextParaRemoved ) )
981 if( pTextHint->GetValue() == EE_PARA_ALL )
983 mnParasChanged = -1;
985 else
987 mnHintId = pTextHint->GetId();
988 mnParaIndex = pTextHint->GetValue();
989 ++mnParasChanged;
995 /** Query number of paragraphs changed during queue processing.
997 @return number of changed paragraphs, -1 for
998 "every paragraph changed"
1000 sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; }
1001 /** Query index of last added/removed paragraph
1003 @return index of lastly added paragraphs, -1 for none
1004 added so far.
1006 sal_Int32 GetParaIndex() const { return mnParaIndex; }
1007 /** Query hint id of last interesting event
1009 @return hint id of last interesting event (REMOVED/INSERTED).
1011 SfxHintId GetHintId() const { return mnHintId; }
1013 private:
1014 /** number of paragraphs changed during queue processing. -1 for
1015 "every paragraph changed"
1017 sal_Int32 mnParasChanged;
1018 /// index of paragraph added/removed last
1019 sal_Int32 mnParaIndex;
1020 /// TextHint ID (removed/inserted) of last interesting event
1021 SfxHintId mnHintId;
1024 void AccessibleTextHelper_Impl::ProcessQueue()
1026 // inspect queue for paragraph insert/remove events. If there
1027 // is exactly _one_ of those in the queue, and the number of
1028 // paragraphs has changed by exactly one, use that event to
1029 // determine a priori which paragraph was added/removed. This
1030 // is necessary, since I must sync right here with the
1031 // EditEngine state (number of paragraphs etc.), since I'm
1032 // potentially sending listener events right away.
1033 AccessibleTextHelper_QueueFunctor aFunctor;
1034 maEventQueue.ForEach( aFunctor );
1036 const sal_Int32 nNewParas( GetTextForwarder().GetParagraphCount() );
1037 const sal_Int32 nCurrParas( maParaManager.GetNum() );
1039 // whether every paragraph already is updated (no need to
1040 // repeat that later on, e.g. for PARA_MOVED events)
1041 bool bEverythingUpdated( false );
1043 if( labs( nNewParas - nCurrParas ) == 1 &&
1044 aFunctor.GetNumberOfParasChanged() == 1 )
1046 // #103483# Exactly one paragraph added/removed. This is
1047 // the normal case, optimize event handling here.
1049 if( aFunctor.GetHintId() == SfxHintId::TextParaInserted )
1051 // update num of paras
1052 maParaManager.SetNum( nNewParas );
1054 // release everything from the insertion position until the end
1055 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1057 // TODO: Clarify whether this behaviour _really_ saves
1058 // anybody anything!
1059 // update children, _don't_ broadcast
1060 UpdateVisibleChildren( false );
1061 UpdateBoundRect();
1063 // send insert event
1064 // #109864# Enforce creation of this paragraph
1067 GotPropertyEvent( uno::makeAny( getAccessibleChild( aFunctor.GetParaIndex() -
1068 mnFirstVisibleChild + GetStartIndex() ) ),
1069 AccessibleEventId::CHILD );
1071 catch( const uno::Exception& )
1073 OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph");
1076 else if( aFunctor.GetHintId() == SfxHintId::TextParaRemoved )
1078 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
1079 ::std::advance( begin, aFunctor.GetParaIndex() );
1080 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
1081 ::std::advance( end, 1 );
1083 // #i61812# remember para to be removed for later notification
1084 // AFTER the new state is applied (that after the para got removed)
1085 ::uno::Reference< XAccessible > xPara(begin->first.get().get());
1087 // release everything from the remove position until the end
1088 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1090 // update num of paras
1091 maParaManager.SetNum( nNewParas );
1093 // TODO: Clarify whether this behaviour _really_ saves
1094 // anybody anything!
1095 // update children, _don't_ broadcast
1096 UpdateVisibleChildren( false );
1097 UpdateBoundRect();
1099 // #i61812# notification for removed para
1100 if (xPara.is())
1101 FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny( xPara) );
1103 #ifdef DBG_UTIL
1104 else
1105 OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue() invalid hint id");
1106 #endif
1108 else if( nNewParas != nCurrParas )
1110 // release all paras
1111 maParaManager.Release(0, nCurrParas);
1113 // update num of paras
1114 maParaManager.SetNum( nNewParas );
1116 // #109864# create from scratch, don't broadcast
1117 UpdateVisibleChildren( false );
1118 UpdateBoundRect();
1120 // number of paragraphs somehow changed - but we have no
1121 // chance determining how. Thus, throw away everything and
1122 // create from scratch.
1123 // (child events should be broadcast after the changes are done...)
1124 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
1126 // no need for further updates later on
1127 bEverythingUpdated = true;
1130 while( !maEventQueue.IsEmpty() )
1132 ::std::unique_ptr< SfxHint > pHint( maEventQueue.PopFront() );
1133 if (pHint)
1135 const SfxHint& rHint = *pHint;
1137 // Note, if you add events here, you need to update the AccessibleTextEventQueue::Append
1138 // code, because only the events we process here, are actually queued there.
1143 if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1145 const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1147 switch( pSdrHint->GetKind() )
1149 case SdrHintKind::BeginEdit:
1151 if(!IsActive())
1153 break;
1155 // change children state
1156 maParaManager.SetActive();
1158 // per definition, edit mode text has the focus
1159 SetFocus( true );
1160 break;
1163 case SdrHintKind::EndEdit:
1165 // focused child now loses focus
1166 ESelection aSelection;
1167 if( GetEditViewForwarder().GetSelection( aSelection ) )
1168 SetChildFocus( aSelection.nEndPara, false );
1170 // change children state
1171 maParaManager.SetActive( false );
1173 maLastSelection = ESelection( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND,
1174 EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND);
1175 break;
1177 default:
1178 break;
1181 else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1183 switch( pEditSourceHint->GetId() )
1185 case SfxHintId::EditSourceParasMoved:
1187 DBG_ASSERT( pEditSourceHint->GetStartValue() < GetTextForwarder().GetParagraphCount() &&
1188 pEditSourceHint->GetEndValue() < GetTextForwarder().GetParagraphCount(),
1189 "AccessibleTextHelper_Impl::NotifyHdl: Invalid notification");
1191 if( !bEverythingUpdated )
1193 ParagraphsMoved(pEditSourceHint->GetStartValue(),
1194 pEditSourceHint->GetValue(),
1195 pEditSourceHint->GetEndValue());
1197 // in all cases, check visibility afterwards.
1198 UpdateVisibleChildren();
1200 break;
1203 case SfxHintId::EditSourceSelectionChanged:
1204 // notify listeners
1207 UpdateSelection();
1209 // maybe we're not in edit mode (this is not an error)
1210 catch( const uno::Exception& ) {}
1211 break;
1212 default: break;
1215 else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1217 const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
1219 switch( pTextHint->GetId() )
1221 case SfxHintId::TextModified:
1223 // notify listeners
1224 sal_Int32 nPara( pTextHint->GetValue() );
1226 // #108900# Delegate change event to children
1227 AccessibleTextHelper_ChildrenTextChanged aNotifyChildrenFunctor;
1229 if( nPara == EE_PARA_ALL )
1231 // #108900# Call every child
1232 ::std::for_each( maParaManager.begin(), maParaManager.end(),
1233 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1235 else
1236 if( nPara < nParas )
1238 // #108900# Call child at index nPara
1239 ::std::for_each( maParaManager.begin()+nPara, maParaManager.begin()+nPara+1,
1240 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1242 break;
1245 case SfxHintId::TextParaInserted:
1246 // already happened above
1247 break;
1249 case SfxHintId::TextParaRemoved:
1250 // already happened above
1251 break;
1253 case SfxHintId::TextHeightChanged:
1254 // visibility changed, done below
1255 break;
1257 case SfxHintId::TextViewScrolled:
1258 // visibility changed, done below
1259 break;
1260 default: break;
1263 // in all cases, check visibility afterwards.
1264 UpdateVisibleChildren();
1265 UpdateBoundRect();
1267 else if ( dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1269 // just check visibility
1270 UpdateVisibleChildren();
1271 UpdateBoundRect();
1273 // it's VITAL to keep the SfxSimpleHint last! It's the base of some classes above!
1274 else if( rHint.GetId() == SfxHintId::Dying)
1276 // edit source is dying under us, become defunc then
1279 // make edit source inaccessible
1280 // Note: cannot destroy it here, since we're called from there!
1281 ShutdownEditSource();
1283 catch( const uno::Exception& ) {}
1286 catch( const uno::Exception& )
1288 DBG_UNHANDLED_EXCEPTION("svx");
1294 void AccessibleTextHelper_Impl::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
1296 // precondition: solar mutex locked
1297 DBG_TESTSOLARMUTEX();
1299 // precondition: not in a recursion
1300 if( mbInNotify )
1301 return;
1303 mbInNotify = true;
1307 // Process notification event, arranged in order of likelihood of
1308 // occurrence to avoid unnecessary dynamic_cast. Note that
1309 // SvxEditSourceHint is derived from TextHint, so has to be checked
1310 // before that.
1311 if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1313 const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1314 // process drawing layer events right away, if not
1315 // within an open EE notification frame. Otherwise,
1316 // event processing would be delayed until next EE
1317 // notification sequence.
1318 maEventQueue.Append( *pSdrHint );
1320 else if( const SvxViewChangedHint* pViewHint = dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1322 // process visibility right away, if not within an
1323 // open EE notification frame. Otherwise, event
1324 // processing would be delayed until next EE
1325 // notification sequence.
1326 maEventQueue.Append( *pViewHint );
1328 else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1330 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1331 maEventQueue.Append( *pEditSourceHint );
1333 else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1335 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1336 if(pTextHint->GetId() == SfxHintId::TextProcessNotifications)
1337 ProcessQueue();
1338 else
1339 maEventQueue.Append( *pTextHint );
1341 // it's VITAL to keep the SfxHint last! It's the base of the classes above!
1342 else if( rHint.GetId() == SfxHintId::Dying )
1344 // handle this event _at once_, because after that, objects are invalid
1345 // edit source is dying under us, become defunc then
1346 maEventQueue.Clear();
1349 // make edit source inaccessible
1350 // Note: cannot destroy it here, since we're called from there!
1351 ShutdownEditSource();
1353 catch( const uno::Exception& ) {}
1356 catch( const uno::Exception& )
1358 DBG_UNHANDLED_EXCEPTION("svx");
1359 mbInNotify = false;
1362 mbInNotify = false;
1365 void AccessibleTextHelper_Impl::Dispose()
1367 if( getNotifierClientId() != -1 )
1371 // #106234# Unregister from EventNotifier
1372 ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() );
1373 SAL_INFO("svx", "disposed ID: " << mnNotifierClientId );
1375 catch( const uno::Exception& ) {}
1377 mnNotifierClientId = -1;
1382 // dispose children
1383 maParaManager.Dispose();
1385 catch( const uno::Exception& ) {}
1387 // quit listen on stale edit source
1388 if( maEditSource.IsValid() )
1389 EndListening( maEditSource.GetBroadcaster() );
1391 // clear references
1392 maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
1393 mxFrontEnd = nullptr;
1396 void AccessibleTextHelper_Impl::FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const
1398 // -- object locked --
1399 AccessibleEventObject aEvent;
1401 osl::MutexGuard aGuard(maMutex);
1403 DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper::FireEvent: no event source set");
1405 if (mxFrontEnd.is())
1406 aEvent = AccessibleEventObject(mxFrontEnd->getAccessibleContext(), nEventId,
1407 rNewValue, rOldValue);
1408 else
1409 aEvent = AccessibleEventObject(uno::Reference<uno::XInterface>(), nEventId,
1410 rNewValue, rOldValue);
1412 // no locking necessary, FireEvent internally copies listeners
1413 // if someone removes/adds in between Further locking,
1414 // actually, might lead to deadlocks, since we're calling out
1415 // of this object
1417 // -- until here --
1419 FireEvent(aEvent);
1422 void AccessibleTextHelper_Impl::FireEvent( const AccessibleEventObject& rEvent ) const
1424 // #102261# Call global queue for focus events
1425 if( rEvent.EventId == AccessibleStateType::FOCUSED )
1426 vcl::unohelper::NotifyAccessibleStateEventGlobally( rEvent );
1428 // #106234# Delegate to EventNotifier
1429 ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(),
1430 rEvent );
1433 // XAccessibleContext
1434 sal_Int32 AccessibleTextHelper_Impl::getAccessibleChildCount() const
1436 return mnLastVisibleChild - mnFirstVisibleChild + 1;
1439 uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int32 i )
1441 i -= GetStartIndex();
1443 if( 0 > i || i >= getAccessibleChildCount() ||
1444 GetTextForwarder().GetParagraphCount() <= i )
1446 throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd);
1449 DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set");
1451 if( mxFrontEnd.is() )
1452 return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first;
1453 else
1454 return nullptr;
1457 void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1459 if( getNotifierClientId() != -1 )
1460 ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener );
1463 void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1465 if( getNotifierClientId() != -1 )
1467 const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener );
1468 if ( !nListenerCount )
1470 // no listeners anymore
1471 // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client),
1472 // and at least to us not firing any events anymore, in case somebody calls
1473 // NotifyAccessibleEvent, again
1474 ::comphelper::AccessibleEventNotifier::TClientId nId( getNotifierClientId() );
1475 mnNotifierClientId = -1;
1476 ::comphelper::AccessibleEventNotifier::revokeClient( nId );
1481 uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint )
1483 // make given position relative
1484 if( !mxFrontEnd.is() )
1485 throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1487 uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext();
1489 if( !xFrontEndContext.is() )
1490 throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1492 uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW );
1494 // #103862# No longer need to make given position relative
1495 Point aPoint( _aPoint.X, _aPoint.Y );
1497 // respect EditEngine offset to surrounding shape/cell
1498 aPoint -= GetOffset();
1500 // convert to EditEngine coordinate system
1501 SvxTextForwarder& rCacheTF = GetTextForwarder();
1502 Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) );
1504 // iterate over all visible children (including those not yet created)
1505 sal_Int32 nChild;
1506 for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild )
1508 DBG_ASSERT(nChild >= 0,
1509 "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow");
1511 tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) );
1513 if( aParaBounds.IsInside( aLogPoint ) )
1514 return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() );
1517 // found none
1518 return nullptr;
1522 // AccessibleTextHelper implementation (simply forwards to impl)
1524 AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) :
1525 mpImpl( new AccessibleTextHelper_Impl() )
1527 SolarMutexGuard aGuard;
1529 SetEditSource( std::move(pEditSource) );
1532 AccessibleTextHelper::~AccessibleTextHelper()
1536 const SvxEditSource& AccessibleTextHelper::GetEditSource() const
1538 #ifdef DBG_UTIL
1539 mpImpl->CheckInvariants();
1541 const SvxEditSource& aEditSource = mpImpl->GetEditSource();
1543 mpImpl->CheckInvariants();
1545 return aEditSource;
1546 #else
1547 return mpImpl->GetEditSource();
1548 #endif
1551 void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
1553 #ifdef DBG_UTIL
1554 // precondition: solar mutex locked
1555 DBG_TESTSOLARMUTEX();
1557 mpImpl->CheckInvariants();
1558 #endif
1560 mpImpl->SetEditSource( std::move(pEditSource) );
1562 #ifdef DBG_UTIL
1563 mpImpl->CheckInvariants();
1564 #endif
1567 void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface )
1569 #ifdef DBG_UTIL
1570 mpImpl->CheckInvariants();
1571 #endif
1573 mpImpl->SetEventSource( rInterface );
1575 #ifdef DBG_UTIL
1576 mpImpl->CheckInvariants();
1577 #endif
1580 void AccessibleTextHelper::SetFocus( bool bHaveFocus )
1582 #ifdef DBG_UTIL
1583 // precondition: solar mutex locked
1584 DBG_TESTSOLARMUTEX();
1586 mpImpl->CheckInvariants();
1587 #endif
1589 mpImpl->SetFocus( bHaveFocus );
1591 #ifdef DBG_UTIL
1592 mpImpl->CheckInvariants();
1593 #endif
1596 bool AccessibleTextHelper::HaveFocus()
1598 #ifdef DBG_UTIL
1599 mpImpl->CheckInvariants();
1601 bool bRet( mpImpl->HaveFocus() );
1603 mpImpl->CheckInvariants();
1605 return bRet;
1606 #else
1607 return mpImpl->HaveFocus();
1608 #endif
1611 void AccessibleTextHelper::SetOffset( const Point& rPoint )
1613 #ifdef DBG_UTIL
1614 // precondition: solar mutex locked
1615 DBG_TESTSOLARMUTEX();
1617 mpImpl->CheckInvariants();
1618 #endif
1620 mpImpl->SetOffset( rPoint );
1622 #ifdef DBG_UTIL
1623 mpImpl->CheckInvariants();
1624 #endif
1627 void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset )
1629 #ifdef DBG_UTIL
1630 // precondition: solar mutex locked
1631 DBG_TESTSOLARMUTEX();
1633 mpImpl->CheckInvariants();
1634 #endif
1636 mpImpl->SetStartIndex( nOffset );
1638 #ifdef DBG_UTIL
1639 mpImpl->CheckInvariants();
1640 #endif
1643 sal_Int32 AccessibleTextHelper::GetStartIndex() const
1645 #ifdef DBG_UTIL
1646 mpImpl->CheckInvariants();
1648 sal_Int32 nOffset = mpImpl->GetStartIndex();
1650 mpImpl->CheckInvariants();
1652 return nOffset;
1653 #else
1654 return mpImpl->GetStartIndex();
1655 #endif
1658 void AccessibleTextHelper::SetAdditionalChildStates( const VectorOfStates& rChildStates )
1660 mpImpl->SetAdditionalChildStates( rChildStates );
1663 void AccessibleTextHelper::UpdateChildren()
1665 #ifdef DBG_UTIL
1666 // precondition: solar mutex locked
1667 DBG_TESTSOLARMUTEX();
1669 mpImpl->CheckInvariants();
1670 #endif
1672 mpImpl->UpdateVisibleChildren();
1673 mpImpl->UpdateBoundRect();
1675 mpImpl->UpdateSelection();
1677 #ifdef DBG_UTIL
1678 mpImpl->CheckInvariants();
1679 #endif
1682 void AccessibleTextHelper::Dispose()
1684 // As Dispose calls ShutdownEditSource, which in turn
1685 // deregisters as listener on the edit source, have to lock
1686 // here
1687 SolarMutexGuard aGuard;
1689 #ifdef DBG_UTIL
1690 mpImpl->CheckInvariants();
1691 #endif
1693 mpImpl->Dispose();
1695 #ifdef DBG_UTIL
1696 mpImpl->CheckInvariants();
1697 #endif
1700 // XAccessibleContext
1701 sal_Int32 AccessibleTextHelper::GetChildCount() const
1703 SolarMutexGuard aGuard;
1705 #ifdef DBG_UTIL
1706 mpImpl->CheckInvariants();
1708 sal_Int32 nRet = mpImpl->getAccessibleChildCount();
1710 mpImpl->CheckInvariants();
1712 return nRet;
1713 #else
1714 return mpImpl->getAccessibleChildCount();
1715 #endif
1718 uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int32 i )
1720 SolarMutexGuard aGuard;
1722 #ifdef DBG_UTIL
1723 mpImpl->CheckInvariants();
1725 uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i );
1727 mpImpl->CheckInvariants();
1729 return xRet;
1730 #else
1731 return mpImpl->getAccessibleChild( i );
1732 #endif
1735 void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1737 #ifdef DBG_UTIL
1738 mpImpl->CheckInvariants();
1740 mpImpl->addAccessibleEventListener( xListener );
1742 mpImpl->CheckInvariants();
1743 #else
1744 mpImpl->addAccessibleEventListener( xListener );
1745 #endif
1748 void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1750 #ifdef DBG_UTIL
1751 mpImpl->CheckInvariants();
1753 mpImpl->removeAccessibleEventListener( xListener );
1755 mpImpl->CheckInvariants();
1756 #else
1757 mpImpl->removeAccessibleEventListener( xListener );
1758 #endif
1761 // XAccessibleComponent
1762 uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint )
1764 SolarMutexGuard aGuard;
1766 #ifdef DBG_UTIL
1767 mpImpl->CheckInvariants();
1769 uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint );
1771 mpImpl->CheckInvariants();
1773 return xChild;
1774 #else
1775 return mpImpl->getAccessibleAtPoint( aPoint );
1776 #endif
1779 } // end of namespace accessibility
1782 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */