1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/config.h>
21 
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>
42 
43 
44 // Project-local header
45 
46 
47 #include "AccessibleTextEventQueue.hxx"
48 #include <svx/AccessibleTextHelper.hxx>
49 
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>
61 
62 using namespace ::com::sun::star;
63 using namespace ::com::sun::star::accessibility;
64 
65 namespace accessibility
66 {
67 
68 // AccessibleTextHelper_Impl declaration
69 
70     template < typename first_type, typename second_type >
makeSortedPair(first_type first,second_type second)71         static ::std::pair< first_type, second_type > makeSortedPair( first_type   first,
72                                                                                  second_type    second  )
73     {
74         if( first > second )
75             return ::std::make_pair( second, first );
76         else
77             return ::std::make_pair( first, second );
78     }
79 
80     class AccessibleTextHelper_Impl : public SfxListener
81     {
82     public:
83         typedef ::std::vector< sal_Int16 > VectorOfStates;
84 
85         // receive pointer to our frontend class and view window
86         AccessibleTextHelper_Impl();
87         virtual ~AccessibleTextHelper_Impl() override;
88 
89         // XAccessibleContext child handling methods
90         sal_Int32 getAccessibleChildCount() const;
91         uno::Reference< XAccessible > getAccessibleChild( sal_Int32 i );
92 
93         // XAccessibleEventBroadcaster child related methods
94         void addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener );
95         void removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener );
96 
97         // XAccessibleComponent child related methods
98         uno::Reference< XAccessible > getAccessibleAtPoint( const awt::Point& aPoint );
99 
100         SvxEditSourceAdapter& GetEditSource() const;
101 
102         void SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource );
103 
SetEventSource(const uno::Reference<XAccessible> & rInterface)104         void SetEventSource( const uno::Reference< XAccessible >& rInterface )
105         {
106             mxFrontEnd = rInterface;
107         }
108 
109         void SetOffset( const Point& );
GetOffset() const110         Point GetOffset() const
111         {
112             ::osl::MutexGuard aGuard( maMutex ); Point aPoint( maOffset );
113             return aPoint;
114         }
115 
116         void SetStartIndex( sal_Int32 nOffset );
GetStartIndex() const117         sal_Int32 GetStartIndex() const
118         {
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;
123         }
124 
125         void SetAdditionalChildStates( const VectorOfStates& rChildStates );
126 
127         void Dispose();
128 
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;
132 
133         void SetFocus( bool bHaveFocus );
HaveFocus()134         bool HaveFocus()
135         {
136             // No locking of solar mutex here, since we rely on the fact
137             // that sal_Bool access is atomic
138             return mbThisHasFocus;
139         }
140         void SetChildFocus( sal_Int32 nChild, bool bHaveFocus );
141         void SetShapeFocus( bool bHaveFocus );
142         void ChangeChildFocus( sal_Int32 nNewChild );
143 
144 #ifdef DBG_UTIL
145         void CheckInvariants() const;
146 #endif
147 
148         // checks all children for visibility, throws away invisible ones
149         void UpdateVisibleChildren( bool bBroadcastEvents=true );
150 
151         // check all children for changes in position and size
152         void UpdateBoundRect();
153 
154         // calls SetSelection on the forwarder and updates maLastSelection
155         // cache.
156         void UpdateSelection();
157 
158     private:
159 
160         // Process event queue
161         void ProcessQueue();
162 
163         // syntactic sugar for FireEvent
GotPropertyEvent(const uno::Any & rNewValue,const sal_Int16 nEventId) const164         void GotPropertyEvent( const uno::Any& rNewValue, const sal_Int16 nEventId ) const { FireEvent( nEventId, rNewValue ); }
165 
166         // shutdown usage of current edit source on myself and the children.
167         void ShutdownEditSource();
168 
169         void ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast );
170 
171         virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
172 
getNotifierClientId() const173         int getNotifierClientId() const { return mnNotifierClientId; }
174 
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;
181 
182         // are we in edit mode?
183         bool IsActive() const;
184 
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;
189 
190         // a wrapper for the text forwarders (guarded by solar mutex)
191         mutable SvxEditSourceAdapter maEditSource;
192 
193         // store last selection (to correctly report selection changes, guarded by solar mutex)
194         ESelection maLastSelection;
195 
196         // cache range of visible children (guarded by solar mutex)
197         sal_Int32 mnFirstVisibleChild;
198         sal_Int32 mnLastVisibleChild;
199 
200         // offset to add to all our children (unguarded, relying on
201         // the fact that sal_Int32 access is atomic)
202         sal_Int32 mnStartIndex;
203 
204         // the object handling our children (guarded by solar mutex)
205         ::accessibility::AccessibleParaManager maParaManager;
206 
207         // Queued events from Notify() (guarded by solar mutex)
208         AccessibleTextEventQueue maEventQueue;
209 
210         // spin lock to prevent notify in notify (guarded by solar mutex)
211         bool mbInNotify;
212 
213         // whether the object or its children has the focus set (guarded by solar mutex)
214         bool mbGroupHasFocus;
215 
216         // whether we (this object) has the focus set (guarded by solar mutex)
217         bool mbThisHasFocus;
218 
219         mutable ::osl::Mutex maMutex;
220 
221         /// our current offset to the containing shape/cell (guarded by maMutex)
222         Point maOffset;
223 
224         /// client Id from AccessibleEventNotifier
225         int mnNotifierClientId;
226     };
227 
AccessibleTextHelper_Impl()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())
243     {
244         SAL_INFO("svx", "received ID: " << mnNotifierClientId );
245     }
246 
~AccessibleTextHelper_Impl()247     AccessibleTextHelper_Impl::~AccessibleTextHelper_Impl()
248     {
249         SolarMutexGuard aGuard;
250 
251         try
252         {
253             // call Dispose here, too, since we've some resources not
254             // automatically freed otherwise
255             Dispose();
256         }
257         catch( const uno::Exception& ) {}
258     }
259 
GetTextForwarder() const260     SvxTextForwarder& AccessibleTextHelper_Impl::GetTextForwarder() const
261     {
262         if( !maEditSource.IsValid() )
263             throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
264 
265         SvxTextForwarder* pTextForwarder = maEditSource.GetTextForwarder();
266 
267         if( !pTextForwarder )
268             throw uno::RuntimeException("Unable to fetch text forwarder, model might be dead", mxFrontEnd);
269 
270         if( !pTextForwarder->IsValid() )
271             throw uno::RuntimeException("Text forwarder is invalid, model might be dead", mxFrontEnd);
272 
273         return *pTextForwarder;
274     }
275 
GetViewForwarder() const276     SvxViewForwarder& AccessibleTextHelper_Impl::GetViewForwarder() const
277     {
278         if( !maEditSource.IsValid() )
279             throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
280 
281         SvxViewForwarder* pViewForwarder = maEditSource.GetViewForwarder();
282 
283         if( !pViewForwarder )
284             throw uno::RuntimeException("Unable to fetch view forwarder, model might be dead", mxFrontEnd);
285 
286         if( !pViewForwarder->IsValid() )
287             throw uno::RuntimeException("View forwarder is invalid, model might be dead", mxFrontEnd);
288 
289         return *pViewForwarder;
290     }
291 
GetEditViewForwarder() const292     SvxEditViewForwarder& AccessibleTextHelper_Impl::GetEditViewForwarder() const
293     {
294         if( !maEditSource.IsValid() )
295             throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
296 
297         SvxEditViewForwarder* pViewForwarder = maEditSource.GetEditViewForwarder();
298 
299         if( !pViewForwarder )
300         {
301             throw uno::RuntimeException("No edit view forwarder, object not in edit mode", mxFrontEnd);
302         }
303 
304         if( !pViewForwarder->IsValid() )
305         {
306             throw uno::RuntimeException("View forwarder is invalid, object not in edit mode", mxFrontEnd);
307         }
308 
309         return *pViewForwarder;
310     }
311 
GetEditSource() const312     SvxEditSourceAdapter& AccessibleTextHelper_Impl::GetEditSource() const
313     {
314         if( !maEditSource.IsValid() )
315             throw uno::RuntimeException("AccessibleTextHelper_Impl::GetEditSource: no edit source", mxFrontEnd );
316         return maEditSource;
317     }
318 
319     namespace {
320 
321     // functor for sending child events (no stand-alone function, they are maybe not inlined)
322     class AccessibleTextHelper_OffsetChildIndex
323     {
324     public:
AccessibleTextHelper_OffsetChildIndex(sal_Int32 nDifference)325         explicit AccessibleTextHelper_OffsetChildIndex( sal_Int32 nDifference ) : mnDifference(nDifference) {}
operator ()(::accessibility::AccessibleEditableTextPara & rPara)326         void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
327         {
328             rPara.SetIndexInParent( rPara.GetIndexInParent() + mnDifference );
329         }
330 
331     private:
332         const sal_Int32 mnDifference;
333     };
334 
335     }
336 
SetStartIndex(sal_Int32 nOffset)337     void AccessibleTextHelper_Impl::SetStartIndex( sal_Int32 nOffset )
338     {
339         sal_Int32 nOldOffset( mnStartIndex );
340 
341         mnStartIndex = nOffset;
342 
343         if( nOldOffset != nOffset )
344         {
345             // update children
346             AccessibleTextHelper_OffsetChildIndex aFunctor( nOffset - nOldOffset );
347 
348             ::std::for_each( maParaManager.begin(), maParaManager.end(),
349                              AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_OffsetChildIndex > (aFunctor) );
350         }
351     }
352 
SetAdditionalChildStates(const VectorOfStates & rChildStates)353     void AccessibleTextHelper_Impl::SetAdditionalChildStates( const VectorOfStates& rChildStates )
354     {
355         maParaManager.SetAdditionalChildStates( rChildStates );
356     }
357 
SetChildFocus(sal_Int32 nChild,bool bHaveFocus)358     void AccessibleTextHelper_Impl::SetChildFocus( sal_Int32 nChild, bool bHaveFocus )
359     {
360         if( bHaveFocus )
361         {
362             if( mbThisHasFocus )
363                 SetShapeFocus( false );
364 
365             maParaManager.SetFocus( nChild );
366 
367             // we just received the focus, also send caret event then
368             UpdateSelection();
369 
370             SAL_INFO("svx", "Paragraph " << nChild << " received focus");
371         }
372         else
373         {
374             maParaManager.SetFocus( -1 );
375 
376             SAL_INFO("svx", "Paragraph " << nChild << " lost focus");
377 
378             if( mbGroupHasFocus )
379                 SetShapeFocus( true );
380         }
381     }
382 
ChangeChildFocus(sal_Int32 nNewChild)383     void AccessibleTextHelper_Impl::ChangeChildFocus( sal_Int32 nNewChild )
384     {
385         if( mbThisHasFocus )
386             SetShapeFocus( false );
387 
388         mbGroupHasFocus = true;
389         maParaManager.SetFocus( nNewChild );
390 
391         SAL_INFO("svx", "Paragraph " << nNewChild << " received focus");
392     }
393 
SetShapeFocus(bool bHaveFocus)394     void AccessibleTextHelper_Impl::SetShapeFocus( bool bHaveFocus )
395     {
396         bool bOldFocus( mbThisHasFocus );
397 
398         mbThisHasFocus = bHaveFocus;
399 
400         if( bOldFocus == bHaveFocus )
401             return;
402 
403         if( bHaveFocus )
404         {
405             if( mxFrontEnd.is() )
406             {
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
411                 {
412                     AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable();
413                     if (pAccTable)
414                         pAccTable->SetStateDirectly(AccessibleStateType::FOCUSED);
415                 }
416             }
417             SAL_INFO("svx", "Parent object received focus" );
418         }
419         else
420         {
421             // The focus state should be reset directly on table.
422             //LostPropertyEvent( uno::makeAny(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED );
423             if( mxFrontEnd.is() )
424             {
425                 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
426                 if ( !pAccessibleCell )
427                     FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::makeAny(AccessibleStateType::FOCUSED) );
428                 else
429                 {
430                     AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable();
431                     if (pAccTable)
432                         pAccTable->ResetStateDirectly(AccessibleStateType::FOCUSED);
433                 }
434             }
435             SAL_INFO("svx", "Parent object lost focus" );
436         }
437     }
438 
SetFocus(bool bHaveFocus)439     void AccessibleTextHelper_Impl::SetFocus( bool bHaveFocus )
440     {
441         bool bOldFocus( mbGroupHasFocus );
442 
443         mbGroupHasFocus = bHaveFocus;
444 
445         if( IsActive() )
446         {
447             try
448             {
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 );
453             }
454             catch( const uno::Exception& ) {}
455         }
456         else if( bOldFocus != bHaveFocus )
457         {
458             SetShapeFocus( bHaveFocus );
459         }
460 
461         SAL_INFO("svx", "focus changed, Object " << this << ", state: " << (bHaveFocus ? "focused" : "not focused") );
462     }
463 
IsActive() const464     bool AccessibleTextHelper_Impl::IsActive() const
465     {
466         try
467         {
468             SvxEditSource& rEditSource = GetEditSource();
469             SvxEditViewForwarder* pViewForwarder = rEditSource.GetEditViewForwarder();
470 
471             if( !pViewForwarder )
472                 return false;
473 
474             if( mxFrontEnd.is() )
475             {
476                 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
477                 if ( pAccessibleCell )
478                 {
479                     sdr::table::CellRef xCell = pAccessibleCell->getCellRef();
480                     if ( xCell.is() )
481                         return xCell->IsActiveCell();
482                 }
483             }
484             return pViewForwarder->IsValid();
485         }
486         catch( const uno::RuntimeException& )
487         {
488             return false;
489         }
490     }
491 
UpdateSelection()492     void AccessibleTextHelper_Impl::UpdateSelection()
493     {
494         try
495         {
496             ESelection aSelection;
497             if( GetEditViewForwarder().GetSelection( aSelection ) )
498             {
499                 if( maLastSelection != aSelection &&
500                     aSelection.nEndPara < maParaManager.GetNum() )
501                 {
502                     // #103998# Not that important, changed from assertion to trace
503                     if( mbThisHasFocus )
504                     {
505                         SAL_INFO("svx", "Parent has focus!");
506                     }
507 
508                     sal_Int32 nMaxValidParaIndex( GetTextForwarder().GetParagraphCount() - 1 );
509 
510                     // notify all affected paragraphs (TODO: may be suboptimal,
511                     // since some paragraphs might stay selected)
512                     if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND )
513                     {
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 )
518                         {
519                             if( maLastSelection.nEndPara < maParaManager.GetNum() )
520                             {
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) );
526                             }
527 
528                             ChangeChildFocus( aSelection.nEndPara );
529 
530                             SAL_INFO(
531                                 "svx",
532                                 "focus changed, Object: " << this
533                                     << ", Paragraph: " << aSelection.nEndPara
534                                     << ", Last paragraph: "
535                                     << maLastSelection.nEndPara);
536                         }
537                     }
538 
539                     // #100530# no caret events if not focused.
540                     if( mbGroupHasFocus )
541                     {
542                         uno::Any aOldCursor;
543 
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 )
548                         {
549                             aOldCursor <<= maLastSelection.nEndPos;
550                         }
551                         else
552                         {
553                             aOldCursor <<= static_cast<sal_Int32>(-1);
554                         }
555 
556                         maParaManager.FireEvent( aSelection.nEndPara,
557                                                  aSelection.nEndPara+1,
558                                                  AccessibleEventId::CARET_CHANGED,
559                                                  uno::makeAny(aSelection.nEndPos),
560                                                  aOldCursor );
561                     }
562 
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);
570 
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 ) ) );
575 
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 ) ) );
580 
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 )
586                     {
587                         // last selection is undefined
588                         // use method <ESelection::HasRange()> (#i27299#)
589                         if ( aSelection.HasRange() )
590                         {
591                             // selection was undefined, now is on
592                             maParaManager.FireEvent( sortedSelection.first,
593                                                      sortedSelection.second+1,
594                                                      nTextSelChgEventId );
595                         }
596                     }
597                     else
598                     {
599                         // last selection is valid
600                         // use method <ESelection::HasRange()> (#i27299#)
601                         if ( maLastSelection.HasRange() &&
602                              !aSelection.HasRange() )
603                         {
604                             // selection was on, now is empty
605                             maParaManager.FireEvent( sortedLastSelection.first,
606                                                      sortedLastSelection.second+1,
607                                                      nTextSelChgEventId );
608                         }
609                         // use method <ESelection::HasRange()> (#i27299#)
610                         else if( !maLastSelection.HasRange() &&
611                                  aSelection.HasRange() )
612                         {
613                             // selection was empty, now is on
614                             maParaManager.FireEvent( sortedSelection.first,
615                                                      sortedSelection.second+1,
616                                                      nTextSelChgEventId );
617                         }
618                         // no event TEXT_SELECTION_CHANGED event, if new and
619                         // last selection are empty. (#i27299#)
620                         else if ( maLastSelection.HasRange() &&
621                                   aSelection.HasRange() )
622                         {
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 )
631                             {
632                                 if ( nPara < aTmpLastSel.nStartPara ||
633                                      nPara > aTmpLastSel.nEndPara )
634                                 {
635                                     // new selection on paragraph <nPara>
636                                     maParaManager.FireEvent( nPara,
637                                                              nTextSelChgEventId );
638                                 }
639                                 else
640                                 {
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 )
656                                     {
657                                         maParaManager.FireEvent(
658                                                     nPara, nTextSelChgEventId );
659                                     }
660                                 }
661                             }
662                             // second submit event for 'old' selections
663                             nPara = aTmpLastSel.nStartPara;
664                             for ( ; nPara <= aTmpLastSel.nEndPara; ++nPara )
665                             {
666                                 if ( nPara < aTmpSel.nStartPara ||
667                                      nPara > aTmpSel.nEndPara )
668                                 {
669                                     maParaManager.FireEvent( nPara,
670                                                              nTextSelChgEventId );
671                                 }
672                             }
673                         }
674                     }
675 
676                     maLastSelection = aSelection;
677                 }
678             }
679         }
680         // no selection? no update actions
681         catch( const uno::RuntimeException& ) {}
682     }
683 
ShutdownEditSource()684     void AccessibleTextHelper_Impl::ShutdownEditSource()
685     {
686         // This should only be called with solar mutex locked, i.e. from the main office thread
687 
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.
693 
694         // invalidate children
695         maParaManager.Dispose();
696         maParaManager.SetNum(0);
697 
698         // lost all children
699         if( mxFrontEnd.is() )
700             FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
701 
702         // quit listen on stale edit source
703         if( maEditSource.IsValid() )
704             EndListening( maEditSource.GetBroadcaster() );
705 
706         maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
707     }
708 
SetEditSource(::std::unique_ptr<SvxEditSource> && pEditSource)709     void AccessibleTextHelper_Impl::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
710     {
711         // This should only be called with solar mutex locked, i.e. from the main office thread
712 
713         // shutdown old edit source
714         ShutdownEditSource();
715 
716         // set new edit source
717         maEditSource.SetEditSource( std::move(pEditSource) );
718 
719         // init child vector to the current child count
720         if( maEditSource.IsValid() )
721         {
722             maParaManager.SetNum( GetTextForwarder().GetParagraphCount() );
723 
724             // listen on new edit source
725             StartListening( maEditSource.GetBroadcaster() );
726 
727             UpdateVisibleChildren();
728         }
729     }
730 
SetOffset(const Point & rPoint)731     void AccessibleTextHelper_Impl::SetOffset( const Point& rPoint )
732     {
733         // guard against non-atomic access to maOffset data structure
734         {
735             ::osl::MutexGuard aGuard( maMutex );
736             maOffset = rPoint;
737         }
738 
739         maParaManager.SetEEOffset( rPoint );
740 
741         // in all cases, check visibility afterwards.
742         UpdateVisibleChildren();
743         UpdateBoundRect();
744     }
745 
UpdateVisibleChildren(bool bBroadcastEvents)746     void AccessibleTextHelper_Impl::UpdateVisibleChildren( bool bBroadcastEvents )
747     {
748         try
749         {
750             SvxTextForwarder& rCacheTF = GetTextForwarder();
751             sal_Int32 nParas=rCacheTF.GetParagraphCount();
752 
753             // GetTextForwarder might have replaced everything, update
754             // paragraph count in case it's outdated
755             maParaManager.SetNum( nParas );
756 
757             mnFirstVisibleChild = -1;
758             mnLastVisibleChild = -2;
759 
760             for( sal_Int32 nCurrPara=0; nCurrPara<nParas; ++nCurrPara )
761             {
762                 if (nCurrPara == 0)
763                     mnFirstVisibleChild = nCurrPara;
764                 mnLastVisibleChild = nCurrPara;
765                 if (mxFrontEnd.is() && bBroadcastEvents)
766                 {
767                     // child not yet created?
768                     ::accessibility::AccessibleParaManager::WeakChild aChild( maParaManager.GetChild(nCurrPara) );
769                     if( aChild.second.Width == 0 &&
770                         aChild.second.Height == 0 )
771                     {
772                         GotPropertyEvent( uno::makeAny( maParaManager.CreateChild( nCurrPara - mnFirstVisibleChild,
773                                                                                    mxFrontEnd, GetEditSource(), nCurrPara ).first ),
774                                           AccessibleEventId::CHILD );
775                     }
776                 }
777             }
778         }
779         catch( const uno::Exception& )
780         {
781             OSL_FAIL("AccessibleTextHelper_Impl::UpdateVisibleChildren error while determining visible children");
782 
783             // something failed - currently no children
784             mnFirstVisibleChild = -1;
785             mnLastVisibleChild = -2;
786             maParaManager.SetNum(0);
787 
788             // lost all children
789             if( bBroadcastEvents )
790                 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
791         }
792     }
793 
794     namespace {
795 
796     // functor for checking changes in paragraph bounding boxes (no stand-alone function, maybe not inlined)
797     class AccessibleTextHelper_UpdateChildBounds
798     {
799     public:
AccessibleTextHelper_UpdateChildBounds()800         explicit AccessibleTextHelper_UpdateChildBounds() {}
operator ()(const::accessibility::AccessibleParaManager::WeakChild & rChild)801         ::accessibility::AccessibleParaManager::WeakChild operator()( const ::accessibility::AccessibleParaManager::WeakChild& rChild )
802         {
803             // retrieve hard reference from weak one
804             auto aHardRef( rChild.first.get() );
805 
806             if( aHardRef.is() )
807             {
808                 awt::Rectangle          aNewRect = aHardRef->getBounds();
809                 const awt::Rectangle&   aOldRect = rChild.second;
810 
811                 if( aNewRect.X != aOldRect.X ||
812                     aNewRect.Y != aOldRect.Y ||
813                     aNewRect.Width != aOldRect.Width ||
814                     aNewRect.Height != aOldRect.Height )
815                 {
816                     // visible data changed
817                     aHardRef->FireEvent( AccessibleEventId::BOUNDRECT_CHANGED );
818 
819                     // update internal bounds
820                     return ::accessibility::AccessibleParaManager::WeakChild( rChild.first, aNewRect );
821                 }
822             }
823 
824             // identity transform
825             return rChild;
826         }
827     };
828 
829     }
830 
UpdateBoundRect()831     void AccessibleTextHelper_Impl::UpdateBoundRect()
832     {
833         // send BOUNDRECT_CHANGED to affected children
834         AccessibleTextHelper_UpdateChildBounds aFunctor;
835         ::std::transform( maParaManager.begin(), maParaManager.end(), maParaManager.begin(), aFunctor );
836     }
837 
838 #ifdef DBG_UTIL
CheckInvariants() const839     void AccessibleTextHelper_Impl::CheckInvariants() const
840     {
841         if( mnFirstVisibleChild >= 0 &&
842             mnFirstVisibleChild > mnLastVisibleChild )
843         {
844             OSL_FAIL( "AccessibleTextHelper: range invalid" );
845         }
846     }
847 #endif
848 
849     namespace {
850 
851     // functor for sending child events (no stand-alone function, they are maybe not inlined)
852     class AccessibleTextHelper_LostChildEvent
853     {
854     public:
AccessibleTextHelper_LostChildEvent(AccessibleTextHelper_Impl & rImpl)855         explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {}
operator ()(const::accessibility::AccessibleParaManager::WeakChild & rPara)856         void operator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara )
857         {
858             // retrieve hard reference from weak one
859             auto aHardRef( rPara.first.get() );
860 
861             if( aHardRef.is() )
862                 mrImpl.FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny<css::uno::Reference<css::accessibility::XAccessible>>(aHardRef) );
863         }
864 
865     private:
866         AccessibleTextHelper_Impl&  mrImpl;
867     };
868 
869     }
870 
ParagraphsMoved(sal_Int32 nFirst,sal_Int32 nMiddle,sal_Int32 nLast)871     void AccessibleTextHelper_Impl::ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast )
872     {
873         const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
874 
875         /* rotate paragraphs
876          * =================
877          *
878          * Three cases:
879          *
880          * 1.
881          *   ... nParagraph ... nParam1 ... nParam2 ...
882          *       |______________[xxxxxxxxxxx]
883          *              becomes
884          *       [xxxxxxxxxxx]|______________
885          *
886          * tail is 0
887          *
888          * 2.
889          *   ... nParam1 ... nParagraph ... nParam2 ...
890          *       [xxxxxxxxxxx|xxxxxxxxxxxxxx]____________
891          *              becomes
892          *       ____________[xxxxxxxxxxx|xxxxxxxxxxxxxx]
893          *
894          * tail is nParagraph - nParam1
895          *
896          * 3.
897          *   ... nParam1 ... nParam2 ... nParagraph ...
898          *       [xxxxxxxxxxx]___________|____________
899          *              becomes
900          *       ___________|____________[xxxxxxxxxxx]
901          *
902          * tail is nParam2 - nParam1
903          */
904 
905         // sort nParagraph, nParam1 and nParam2 in ascending order, calc range
906         if( nMiddle < nFirst )
907         {
908             ::std::swap(nFirst, nMiddle);
909         }
910         else if( nMiddle < nLast )
911         {
912             nLast = nLast + nMiddle - nFirst;
913         }
914         else
915         {
916             ::std::swap(nMiddle, nLast);
917             nLast = nLast + nMiddle - nFirst;
918         }
919 
920         if( !(nFirst < nParas && nMiddle < nParas && nLast < nParas) )
921             return;
922 
923         // since we have no "paragraph index
924         // changed" event on UAA, remove
925         // [first,last] and insert again later (in
926         // UpdateVisibleChildren)
927 
928         // maParaManager.Rotate( nFirst, nMiddle, nLast );
929 
930         // send CHILD_EVENT to affected children
931         ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
932         ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
933 
934         ::std::advance( begin, nFirst );
935         ::std::advance( end, nLast+1 );
936 
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 );
941 
942         ::std::for_each( begin, end, aFunctor );
943 
944         maParaManager.Release(nFirst, nLast+1);
945         // should be no need for UpdateBoundRect, since all affected children are cleared.
946     }
947 
948     namespace {
949 
950     // functor for sending child events (no stand-alone function, they are maybe not inlined)
951     class AccessibleTextHelper_ChildrenTextChanged
952     {
953     public:
operator ()(::accessibility::AccessibleEditableTextPara & rPara)954         void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
955         {
956             rPara.TextChanged();
957         }
958     };
959 
960     /** functor processing queue events
961 
962         Reacts on SfxHintId::TextParaInserted/REMOVED events and stores
963         their content
964      */
965     class AccessibleTextHelper_QueueFunctor
966     {
967     public:
AccessibleTextHelper_QueueFunctor()968         AccessibleTextHelper_QueueFunctor() :
969             mnParasChanged( 0 ),
970             mnParaIndex(-1),
971             mnHintId(SfxHintId::NONE)
972         {}
operator ()(const SfxHint * pEvent)973         void operator()( const SfxHint* pEvent )
974         {
975             if( !pEvent || mnParasChanged == -1 )
976                 return;
977 
978             // determine hint type
979             const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent );
980             const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent );
981 
982             if( !(!pEditSourceHint && pTextHint &&
983                 (pTextHint->GetId() == SfxHintId::TextParaInserted ||
984                  pTextHint->GetId() == SfxHintId::TextParaRemoved )) )
985                 return;
986 
987             if( pTextHint->GetValue() == EE_PARA_ALL )
988             {
989                 mnParasChanged = -1;
990             }
991             else
992             {
993                 mnHintId = pTextHint->GetId();
994                 mnParaIndex = pTextHint->GetValue();
995                 ++mnParasChanged;
996             }
997         }
998 
999         /** Query number of paragraphs changed during queue processing.
1000 
1001             @return number of changed paragraphs, -1 for
1002             "every paragraph changed"
1003         */
GetNumberOfParasChanged() const1004         sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; }
1005         /** Query index of last added/removed paragraph
1006 
1007             @return index of lastly added paragraphs, -1 for none
1008             added so far.
1009         */
GetParaIndex() const1010         sal_Int32 GetParaIndex() const { return mnParaIndex; }
1011         /** Query hint id of last interesting event
1012 
1013             @return hint id of last interesting event (REMOVED/INSERTED).
1014         */
GetHintId() const1015         SfxHintId GetHintId() const { return mnHintId; }
1016 
1017     private:
1018         /** number of paragraphs changed during queue processing. -1 for
1019             "every paragraph changed"
1020         */
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;
1026     };
1027 
1028     }
1029 
ProcessQueue()1030     void AccessibleTextHelper_Impl::ProcessQueue()
1031     {
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 );
1041 
1042         const sal_Int32 nNewParas( GetTextForwarder().GetParagraphCount() );
1043         const sal_Int32 nCurrParas( maParaManager.GetNum() );
1044 
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 );
1048 
1049         if( std::abs( nNewParas - nCurrParas ) == 1 &&
1050             aFunctor.GetNumberOfParasChanged() == 1 )
1051         {
1052             // #103483# Exactly one paragraph added/removed. This is
1053             // the normal case, optimize event handling here.
1054 
1055             if( aFunctor.GetHintId() == SfxHintId::TextParaInserted )
1056             {
1057                 // update num of paras
1058                 maParaManager.SetNum( nNewParas );
1059 
1060                 // release everything from the insertion position until the end
1061                 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1062 
1063                 // TODO: Clarify whether this behaviour _really_ saves
1064                 // anybody anything!
1065                 // update children, _don't_ broadcast
1066                 UpdateVisibleChildren( false );
1067                 UpdateBoundRect();
1068 
1069                 // send insert event
1070                 // #109864# Enforce creation of this paragraph
1071                 try
1072                 {
1073                     GotPropertyEvent( uno::makeAny( getAccessibleChild( aFunctor.GetParaIndex() -
1074                                                                         mnFirstVisibleChild + GetStartIndex() ) ),
1075                                       AccessibleEventId::CHILD );
1076                 }
1077                 catch( const uno::Exception& )
1078                 {
1079                     OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph");
1080                 }
1081             }
1082             else if( aFunctor.GetHintId() == SfxHintId::TextParaRemoved )
1083             {
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 );
1088 
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());
1092 
1093                 // release everything from the remove position until the end
1094                 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1095 
1096                 // update num of paras
1097                 maParaManager.SetNum( nNewParas );
1098 
1099                 // TODO: Clarify whether this behaviour _really_ saves
1100                 // anybody anything!
1101                 // update children, _don't_ broadcast
1102                 UpdateVisibleChildren( false );
1103                 UpdateBoundRect();
1104 
1105                 // #i61812# notification for removed para
1106                 if (xPara.is())
1107                     FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny( xPara) );
1108             }
1109 #ifdef DBG_UTIL
1110             else
1111                 OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue() invalid hint id");
1112 #endif
1113         }
1114         else if( nNewParas != nCurrParas )
1115         {
1116             // release all paras
1117             maParaManager.Release(0, nCurrParas);
1118 
1119             // update num of paras
1120             maParaManager.SetNum( nNewParas );
1121 
1122             // #109864# create from scratch, don't broadcast
1123             UpdateVisibleChildren( false );
1124             UpdateBoundRect();
1125 
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);
1131 
1132             // no need for further updates later on
1133             bEverythingUpdated = true;
1134         }
1135 
1136         while( !maEventQueue.IsEmpty() )
1137         {
1138             ::std::unique_ptr< SfxHint > pHint( maEventQueue.PopFront() );
1139             if (pHint)
1140             {
1141                 const SfxHint& rHint = *pHint;
1142 
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.
1145 
1146                 try
1147                 {
1148 
1149                     if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1150                     {
1151                         const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1152 
1153                         switch( pSdrHint->GetKind() )
1154                         {
1155                             case SdrHintKind::BeginEdit:
1156                             {
1157                                 if(!IsActive())
1158                                 {
1159                                     break;
1160                                 }
1161                                 // change children state
1162                                 maParaManager.SetActive();
1163 
1164                                 // per definition, edit mode text has the focus
1165                                 SetFocus( true );
1166                                 break;
1167                             }
1168 
1169                             case SdrHintKind::EndEdit:
1170                             {
1171                                 // focused child now loses focus
1172                                 ESelection aSelection;
1173                                 if( GetEditViewForwarder().GetSelection( aSelection ) )
1174                                     SetChildFocus( aSelection.nEndPara, false );
1175 
1176                                 // change children state
1177                                 maParaManager.SetActive( false );
1178 
1179                                 maLastSelection = ESelection( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND,
1180                                                               EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND);
1181                                 break;
1182                             }
1183                             default:
1184                                 break;
1185                         }
1186                     }
1187                     else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1188                     {
1189                         switch( pEditSourceHint->GetId() )
1190                         {
1191                             case SfxHintId::EditSourceParasMoved:
1192                             {
1193                                 DBG_ASSERT( pEditSourceHint->GetStartValue() < GetTextForwarder().GetParagraphCount() &&
1194                                             pEditSourceHint->GetEndValue() < GetTextForwarder().GetParagraphCount(),
1195                                             "AccessibleTextHelper_Impl::NotifyHdl: Invalid notification");
1196 
1197                                 if( !bEverythingUpdated )
1198                                 {
1199                                     ParagraphsMoved(pEditSourceHint->GetStartValue(),
1200                                                     pEditSourceHint->GetValue(),
1201                                                     pEditSourceHint->GetEndValue());
1202 
1203                                     // in all cases, check visibility afterwards.
1204                                     UpdateVisibleChildren();
1205                                 }
1206                                 break;
1207                             }
1208 
1209                             case SfxHintId::EditSourceSelectionChanged:
1210                                 // notify listeners
1211                                 try
1212                                 {
1213                                     UpdateSelection();
1214                                 }
1215                                 // maybe we're not in edit mode (this is not an error)
1216                                 catch( const uno::Exception& ) {}
1217                                 break;
1218                             default: break;
1219                         }
1220                     }
1221                     else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1222                     {
1223                         const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
1224 
1225                         switch( pTextHint->GetId() )
1226                         {
1227                             case SfxHintId::TextModified:
1228                             {
1229                                 // notify listeners
1230                                 sal_Int32 nPara( pTextHint->GetValue() );
1231 
1232                                 // #108900# Delegate change event to children
1233                                 AccessibleTextHelper_ChildrenTextChanged aNotifyChildrenFunctor;
1234 
1235                                 if( nPara == EE_PARA_ALL )
1236                                 {
1237                                     // #108900# Call every child
1238                                     ::std::for_each( maParaManager.begin(), maParaManager.end(),
1239                                                      AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1240                                 }
1241                                 else
1242                                     if( nPara < nParas )
1243                                     {
1244                                         // #108900# Call child at index nPara
1245                                         ::std::for_each( maParaManager.begin()+nPara, maParaManager.begin()+nPara+1,
1246                                                          AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1247                                     }
1248                                 break;
1249                             }
1250 
1251                             case SfxHintId::TextParaInserted:
1252                                 // already happened above
1253                                 break;
1254 
1255                             case SfxHintId::TextParaRemoved:
1256                                 // already happened above
1257                                 break;
1258 
1259                             case SfxHintId::TextHeightChanged:
1260                                 // visibility changed, done below
1261                                 break;
1262 
1263                             case SfxHintId::TextViewScrolled:
1264                                 // visibility changed, done below
1265                                 break;
1266                             default: break;
1267                         }
1268 
1269                         // in all cases, check visibility afterwards.
1270                         UpdateVisibleChildren();
1271                         UpdateBoundRect();
1272                     }
1273                     else if ( dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1274                     {
1275                         // just check visibility
1276                         UpdateVisibleChildren();
1277                         UpdateBoundRect();
1278                     }
1279                     // it's VITAL to keep the SfxSimpleHint last! It's the base of some classes above!
1280                     else if( rHint.GetId() == SfxHintId::Dying)
1281                     {
1282                         // edit source is dying under us, become defunc then
1283                         try
1284                         {
1285                             // make edit source inaccessible
1286                             // Note: cannot destroy it here, since we're called from there!
1287                             ShutdownEditSource();
1288                         }
1289                         catch( const uno::Exception& ) {}
1290                     }
1291                 }
1292                 catch( const uno::Exception& )
1293                 {
1294                     DBG_UNHANDLED_EXCEPTION("svx");
1295                 }
1296             }
1297         }
1298     }
1299 
Notify(SfxBroadcaster &,const SfxHint & rHint)1300     void AccessibleTextHelper_Impl::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
1301     {
1302         // precondition: solar mutex locked
1303         DBG_TESTSOLARMUTEX();
1304 
1305         // precondition: not in a recursion
1306         if( mbInNotify )
1307             return;
1308 
1309         mbInNotify = true;
1310 
1311         try
1312         {
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)
1318             {
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 );
1325             }
1326             else if( const SvxViewChangedHint* pViewHint = dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1327             {
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 );
1333             }
1334             else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1335             {
1336                 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1337                 maEventQueue.Append( *pEditSourceHint );
1338             }
1339             else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1340             {
1341                 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1342                 if(pTextHint->GetId() == SfxHintId::TextProcessNotifications)
1343                     ProcessQueue();
1344                 else
1345                     maEventQueue.Append( *pTextHint );
1346             }
1347             // it's VITAL to keep the SfxHint last! It's the base of the classes above!
1348             else if( rHint.GetId() == SfxHintId::Dying )
1349             {
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();
1353                 try
1354                 {
1355                     // make edit source inaccessible
1356                     // Note: cannot destroy it here, since we're called from there!
1357                     ShutdownEditSource();
1358                 }
1359                 catch( const uno::Exception& ) {}
1360             }
1361         }
1362         catch( const uno::Exception& )
1363         {
1364             DBG_UNHANDLED_EXCEPTION("svx");
1365             mbInNotify = false;
1366         }
1367 
1368         mbInNotify = false;
1369     }
1370 
Dispose()1371     void AccessibleTextHelper_Impl::Dispose()
1372     {
1373         if( getNotifierClientId() != -1 )
1374         {
1375             try
1376             {
1377                 // #106234# Unregister from EventNotifier
1378                 ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() );
1379                 SAL_INFO("svx", "disposed ID: " << mnNotifierClientId );
1380             }
1381             catch( const uno::Exception& ) {}
1382 
1383             mnNotifierClientId = -1;
1384         }
1385 
1386         try
1387         {
1388             // dispose children
1389             maParaManager.Dispose();
1390         }
1391         catch( const uno::Exception& ) {}
1392 
1393         // quit listen on stale edit source
1394         if( maEditSource.IsValid() )
1395             EndListening( maEditSource.GetBroadcaster() );
1396 
1397         // clear references
1398         maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
1399         mxFrontEnd = nullptr;
1400     }
1401 
FireEvent(const sal_Int16 nEventId,const uno::Any & rNewValue,const uno::Any & rOldValue) const1402     void AccessibleTextHelper_Impl::FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const
1403     {
1404         // -- object locked --
1405         AccessibleEventObject aEvent;
1406         {
1407             osl::MutexGuard aGuard(maMutex);
1408 
1409             DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper::FireEvent: no event source set");
1410 
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);
1417 
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
1422         }
1423         // -- until here --
1424 
1425         FireEvent(aEvent);
1426     }
1427 
FireEvent(const AccessibleEventObject & rEvent) const1428     void AccessibleTextHelper_Impl::FireEvent( const AccessibleEventObject& rEvent ) const
1429     {
1430         // #102261# Call global queue for focus events
1431         if( rEvent.EventId == AccessibleStateType::FOCUSED )
1432             vcl::unohelper::NotifyAccessibleStateEventGlobally( rEvent );
1433 
1434         // #106234# Delegate to EventNotifier
1435         ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(),
1436                                                          rEvent );
1437     }
1438 
1439     // XAccessibleContext
getAccessibleChildCount() const1440     sal_Int32 AccessibleTextHelper_Impl::getAccessibleChildCount() const
1441     {
1442         return mnLastVisibleChild - mnFirstVisibleChild + 1;
1443     }
1444 
getAccessibleChild(sal_Int32 i)1445     uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int32 i )
1446     {
1447         i -= GetStartIndex();
1448 
1449         if( 0 > i || i >= getAccessibleChildCount() ||
1450             GetTextForwarder().GetParagraphCount() <= i )
1451         {
1452             throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd);
1453         }
1454 
1455         DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set");
1456 
1457         if( mxFrontEnd.is() )
1458             return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first;
1459         else
1460             return nullptr;
1461     }
1462 
addAccessibleEventListener(const uno::Reference<XAccessibleEventListener> & xListener)1463     void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1464     {
1465         if( getNotifierClientId() != -1 )
1466             ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener );
1467     }
1468 
removeAccessibleEventListener(const uno::Reference<XAccessibleEventListener> & xListener)1469     void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1470     {
1471         if( getNotifierClientId() == -1 )
1472             return;
1473 
1474         const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener );
1475         if ( !nListenerCount )
1476         {
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 );
1484         }
1485     }
1486 
getAccessibleAtPoint(const awt::Point & _aPoint)1487     uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint )
1488     {
1489         // make given position relative
1490         if( !mxFrontEnd.is() )
1491             throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1492 
1493         uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext();
1494 
1495         if( !xFrontEndContext.is() )
1496             throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1497 
1498         uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW );
1499 
1500         // #103862# No longer need to make given position relative
1501         Point aPoint( _aPoint.X, _aPoint.Y );
1502 
1503         // respect EditEngine offset to surrounding shape/cell
1504         aPoint -= GetOffset();
1505 
1506         // convert to EditEngine coordinate system
1507         SvxTextForwarder& rCacheTF = GetTextForwarder();
1508         Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) );
1509 
1510         // iterate over all visible children (including those not yet created)
1511         sal_Int32 nChild;
1512         for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild )
1513         {
1514             DBG_ASSERT(nChild >= 0,
1515                        "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow");
1516 
1517             tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) );
1518 
1519             if( aParaBounds.IsInside( aLogPoint ) )
1520                 return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() );
1521         }
1522 
1523         // found none
1524         return nullptr;
1525     }
1526 
1527 
1528     // AccessibleTextHelper implementation (simply forwards to impl)
1529 
AccessibleTextHelper(::std::unique_ptr<SvxEditSource> && pEditSource)1530     AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) :
1531         mpImpl( new AccessibleTextHelper_Impl() )
1532     {
1533         SolarMutexGuard aGuard;
1534 
1535         SetEditSource( std::move(pEditSource) );
1536     }
1537 
~AccessibleTextHelper()1538     AccessibleTextHelper::~AccessibleTextHelper()
1539     {
1540     }
1541 
GetEditSource() const1542     const SvxEditSource& AccessibleTextHelper::GetEditSource() const
1543     {
1544 #ifdef DBG_UTIL
1545         mpImpl->CheckInvariants();
1546 
1547         const SvxEditSource& aEditSource = mpImpl->GetEditSource();
1548 
1549         mpImpl->CheckInvariants();
1550 
1551         return aEditSource;
1552 #else
1553         return mpImpl->GetEditSource();
1554 #endif
1555     }
1556 
SetEditSource(::std::unique_ptr<SvxEditSource> && pEditSource)1557     void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
1558     {
1559 #ifdef DBG_UTIL
1560         // precondition: solar mutex locked
1561         DBG_TESTSOLARMUTEX();
1562 
1563         mpImpl->CheckInvariants();
1564 #endif
1565 
1566         mpImpl->SetEditSource( std::move(pEditSource) );
1567 
1568 #ifdef DBG_UTIL
1569         mpImpl->CheckInvariants();
1570 #endif
1571     }
1572 
SetEventSource(const uno::Reference<XAccessible> & rInterface)1573     void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface )
1574     {
1575 #ifdef DBG_UTIL
1576         mpImpl->CheckInvariants();
1577 #endif
1578 
1579         mpImpl->SetEventSource( rInterface );
1580 
1581 #ifdef DBG_UTIL
1582         mpImpl->CheckInvariants();
1583 #endif
1584     }
1585 
SetFocus(bool bHaveFocus)1586     void AccessibleTextHelper::SetFocus( bool bHaveFocus )
1587     {
1588 #ifdef DBG_UTIL
1589         // precondition: solar mutex locked
1590         DBG_TESTSOLARMUTEX();
1591 
1592         mpImpl->CheckInvariants();
1593 #endif
1594 
1595         mpImpl->SetFocus( bHaveFocus );
1596 
1597 #ifdef DBG_UTIL
1598         mpImpl->CheckInvariants();
1599 #endif
1600     }
1601 
HaveFocus()1602     bool AccessibleTextHelper::HaveFocus()
1603     {
1604 #ifdef DBG_UTIL
1605         mpImpl->CheckInvariants();
1606 
1607         bool bRet( mpImpl->HaveFocus() );
1608 
1609         mpImpl->CheckInvariants();
1610 
1611         return bRet;
1612 #else
1613         return mpImpl->HaveFocus();
1614 #endif
1615     }
1616 
SetOffset(const Point & rPoint)1617     void AccessibleTextHelper::SetOffset( const Point& rPoint )
1618     {
1619 #ifdef DBG_UTIL
1620         // precondition: solar mutex locked
1621         DBG_TESTSOLARMUTEX();
1622 
1623         mpImpl->CheckInvariants();
1624 #endif
1625 
1626         mpImpl->SetOffset( rPoint );
1627 
1628 #ifdef DBG_UTIL
1629         mpImpl->CheckInvariants();
1630 #endif
1631     }
1632 
SetStartIndex(sal_Int32 nOffset)1633     void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset )
1634     {
1635 #ifdef DBG_UTIL
1636         // precondition: solar mutex locked
1637         DBG_TESTSOLARMUTEX();
1638 
1639         mpImpl->CheckInvariants();
1640 #endif
1641 
1642         mpImpl->SetStartIndex( nOffset );
1643 
1644 #ifdef DBG_UTIL
1645         mpImpl->CheckInvariants();
1646 #endif
1647     }
1648 
GetStartIndex() const1649     sal_Int32 AccessibleTextHelper::GetStartIndex() const
1650     {
1651 #ifdef DBG_UTIL
1652         mpImpl->CheckInvariants();
1653 
1654         sal_Int32 nOffset = mpImpl->GetStartIndex();
1655 
1656         mpImpl->CheckInvariants();
1657 
1658         return nOffset;
1659 #else
1660         return mpImpl->GetStartIndex();
1661 #endif
1662     }
1663 
SetAdditionalChildStates(const VectorOfStates & rChildStates)1664     void AccessibleTextHelper::SetAdditionalChildStates( const VectorOfStates& rChildStates )
1665     {
1666         mpImpl->SetAdditionalChildStates( rChildStates );
1667     }
1668 
UpdateChildren()1669     void AccessibleTextHelper::UpdateChildren()
1670     {
1671 #ifdef DBG_UTIL
1672         // precondition: solar mutex locked
1673         DBG_TESTSOLARMUTEX();
1674 
1675         mpImpl->CheckInvariants();
1676 #endif
1677 
1678         mpImpl->UpdateVisibleChildren();
1679         mpImpl->UpdateBoundRect();
1680 
1681         mpImpl->UpdateSelection();
1682 
1683 #ifdef DBG_UTIL
1684         mpImpl->CheckInvariants();
1685 #endif
1686     }
1687 
Dispose()1688     void AccessibleTextHelper::Dispose()
1689     {
1690         // As Dispose calls ShutdownEditSource, which in turn
1691         // deregisters as listener on the edit source, have to lock
1692         // here
1693         SolarMutexGuard aGuard;
1694 
1695 #ifdef DBG_UTIL
1696         mpImpl->CheckInvariants();
1697 #endif
1698 
1699         mpImpl->Dispose();
1700 
1701 #ifdef DBG_UTIL
1702         mpImpl->CheckInvariants();
1703 #endif
1704     }
1705 
1706     // XAccessibleContext
GetChildCount() const1707     sal_Int32 AccessibleTextHelper::GetChildCount() const
1708     {
1709         SolarMutexGuard aGuard;
1710 
1711 #ifdef DBG_UTIL
1712         mpImpl->CheckInvariants();
1713 
1714         sal_Int32 nRet = mpImpl->getAccessibleChildCount();
1715 
1716         mpImpl->CheckInvariants();
1717 
1718         return nRet;
1719 #else
1720         return mpImpl->getAccessibleChildCount();
1721 #endif
1722     }
1723 
GetChild(sal_Int32 i)1724     uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int32 i )
1725     {
1726         SolarMutexGuard aGuard;
1727 
1728 #ifdef DBG_UTIL
1729         mpImpl->CheckInvariants();
1730 
1731         uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i );
1732 
1733         mpImpl->CheckInvariants();
1734 
1735         return xRet;
1736 #else
1737         return mpImpl->getAccessibleChild( i );
1738 #endif
1739     }
1740 
AddEventListener(const uno::Reference<XAccessibleEventListener> & xListener)1741     void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1742     {
1743 #ifdef DBG_UTIL
1744         mpImpl->CheckInvariants();
1745 
1746         mpImpl->addAccessibleEventListener( xListener );
1747 
1748         mpImpl->CheckInvariants();
1749 #else
1750         mpImpl->addAccessibleEventListener( xListener );
1751 #endif
1752     }
1753 
RemoveEventListener(const uno::Reference<XAccessibleEventListener> & xListener)1754     void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1755     {
1756 #ifdef DBG_UTIL
1757         mpImpl->CheckInvariants();
1758 
1759         mpImpl->removeAccessibleEventListener( xListener );
1760 
1761         mpImpl->CheckInvariants();
1762 #else
1763         mpImpl->removeAccessibleEventListener( xListener );
1764 #endif
1765     }
1766 
1767     // XAccessibleComponent
GetAt(const awt::Point & aPoint)1768     uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint )
1769     {
1770         SolarMutexGuard aGuard;
1771 
1772 #ifdef DBG_UTIL
1773         mpImpl->CheckInvariants();
1774 
1775         uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint );
1776 
1777         mpImpl->CheckInvariants();
1778 
1779         return xChild;
1780 #else
1781         return mpImpl->getAccessibleAtPoint( aPoint );
1782 #endif
1783     }
1784 
1785 } // end of namespace accessibility
1786 
1787 
1788 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1789