LibreOffice Module svx (master)  1
AccessibleTextHelper.cxx
Go to the documentation of this file.
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 <mutex>
25 #include <utility>
26 #include <algorithm>
27 #include <sal/log.hxx>
28 #include <com/sun/star/uno/Any.hxx>
29 #include <com/sun/star/uno/Reference.hxx>
30 #include <com/sun/star/awt/Point.hpp>
31 #include <com/sun/star/awt/Rectangle.hpp>
32 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
33 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
34 #include <com/sun/star/accessibility/XAccessible.hpp>
35 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
36 #include <com/sun/star/accessibility/XAccessibleComponent.hpp>
37 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
39 #include <vcl/svapp.hxx>
40 #include <vcl/textdata.hxx>
41 #include <vcl/unohelp.hxx>
42 
43 
44 // Project-local header
45 
46 
49 
50 #include <editeng/unoedhlp.hxx>
51 #include <editeng/unoedprx.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 >
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 
81  {
82  public:
83  typedef ::std::vector< sal_Int16 > VectorOfStates;
84 
85  // receive pointer to our frontend class and view window
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 
104  void SetEventSource( const uno::Reference< XAccessible >& rInterface )
105  {
106  mxFrontEnd = rInterface;
107  }
108 
109  void SetOffset( const Point& );
110  Point GetOffset() const
111  {
112  std::scoped_lock aGuard( maMutex ); Point aPoint( maOffset );
113  return aPoint;
114  }
115 
116  void SetStartIndex( sal_Int32 nOffset );
117  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( 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 );
134  bool HaveFocus() const
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
164  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 
173  int getNotifierClientId() const { return mnNotifierClientId; }
174 
175  // lock solar mutex before
177  // lock solar mutex before
179  // lock solar mutex before
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)
195 
196  // cache range of visible children (guarded by solar mutex)
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)
209 
210  // spin lock to prevent notify in notify (guarded by solar mutex)
212 
213  // whether the object or its children has the focus set (guarded by solar mutex)
215 
216  // whether we (this object) has the focus set (guarded by solar mutex)
218 
219  mutable std::mutex maMutex;
220 
223 
226  };
227 
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 
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 
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 
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 
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 
312  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:
325  explicit AccessibleTextHelper_OffsetChildIndex( sal_Int32 nDifference ) : mnDifference(nDifference) {}
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 
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 
353  void AccessibleTextHelper_Impl::SetAdditionalChildStates( VectorOfStates&& rChildStates )
354  {
355  maParaManager.SetAdditionalChildStates( std::move(rChildStates) );
356  }
357 
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 
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 
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 
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 
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 
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)
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: "
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!
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: "
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
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 
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 
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
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 
728  }
729  }
730 
731  void AccessibleTextHelper_Impl::SetOffset( const Point& rPoint )
732  {
733  // guard against non-atomic access to maOffset data structure
734  {
735  std::scoped_lock aGuard( maMutex );
736  maOffset = rPoint;
737  }
738 
739  maParaManager.SetEEOffset( rPoint );
740 
741  // in all cases, check visibility afterwards.
743  UpdateBoundRect();
744  }
745 
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  if (!maParaManager.HasCreatedChild(nCurrPara))
769  {
770  GotPropertyEvent( uno::makeAny( maParaManager.CreateChild( nCurrPara - mnFirstVisibleChild,
771  mxFrontEnd, GetEditSource(), nCurrPara ).first ),
772  AccessibleEventId::CHILD );
773  }
774  }
775  }
776  }
777  catch( const uno::Exception& )
778  {
779  OSL_FAIL("AccessibleTextHelper_Impl::UpdateVisibleChildren error while determining visible children");
780 
781  // something failed - currently no children
782  mnFirstVisibleChild = -1;
783  mnLastVisibleChild = -2;
784  maParaManager.SetNum(0);
785 
786  // lost all children
787  if( bBroadcastEvents )
788  FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
789  }
790  }
791 
793  {
794  // send BOUNDRECT_CHANGED to affected children
795  for(auto it = maParaManager.begin(); it != maParaManager.end(); ++it)
796  {
797  ::accessibility::AccessibleParaManager::WeakChild& rChild = *it;
798  // retrieve hard reference from weak one
799  auto aHardRef( rChild.first.get() );
800 
801  if( aHardRef.is() )
802  {
803  awt::Rectangle aNewRect = aHardRef->getBounds();
804  const awt::Rectangle& aOldRect = rChild.second;
805 
806  if( aNewRect.X != aOldRect.X ||
807  aNewRect.Y != aOldRect.Y ||
808  aNewRect.Width != aOldRect.Width ||
809  aNewRect.Height != aOldRect.Height )
810  {
811  // visible data changed
812  aHardRef->FireEvent( AccessibleEventId::BOUNDRECT_CHANGED );
813 
814  // update internal bounds
815  rChild = ::accessibility::AccessibleParaManager::WeakChild( rChild.first, aNewRect );
816  }
817  }
818  }
819  }
820 
821 #ifdef DBG_UTIL
823  {
824  if( mnFirstVisibleChild >= 0 &&
826  {
827  OSL_FAIL( "AccessibleTextHelper: range invalid" );
828  }
829  }
830 #endif
831 
832  namespace {
833 
834  // functor for sending child events (no stand-alone function, they are maybe not inlined)
835  class AccessibleTextHelper_LostChildEvent
836  {
837  public:
838  explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {}
839  void operator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara )
840  {
841  // retrieve hard reference from weak one
842  auto aHardRef( rPara.first.get() );
843 
844  if( aHardRef.is() )
845  mrImpl.FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny<css::uno::Reference<css::accessibility::XAccessible>>(aHardRef) );
846  }
847 
848  private:
849  AccessibleTextHelper_Impl& mrImpl;
850  };
851 
852  }
853 
854  void AccessibleTextHelper_Impl::ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast )
855  {
856  const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
857 
858  /* rotate paragraphs
859  * =================
860  *
861  * Three cases:
862  *
863  * 1.
864  * ... nParagraph ... nParam1 ... nParam2 ...
865  * |______________[xxxxxxxxxxx]
866  * becomes
867  * [xxxxxxxxxxx]|______________
868  *
869  * tail is 0
870  *
871  * 2.
872  * ... nParam1 ... nParagraph ... nParam2 ...
873  * [xxxxxxxxxxx|xxxxxxxxxxxxxx]____________
874  * becomes
875  * ____________[xxxxxxxxxxx|xxxxxxxxxxxxxx]
876  *
877  * tail is nParagraph - nParam1
878  *
879  * 3.
880  * ... nParam1 ... nParam2 ... nParagraph ...
881  * [xxxxxxxxxxx]___________|____________
882  * becomes
883  * ___________|____________[xxxxxxxxxxx]
884  *
885  * tail is nParam2 - nParam1
886  */
887 
888  // sort nParagraph, nParam1 and nParam2 in ascending order, calc range
889  if( nMiddle < nFirst )
890  {
891  ::std::swap(nFirst, nMiddle);
892  }
893  else if( nMiddle < nLast )
894  {
895  nLast = nLast + nMiddle - nFirst;
896  }
897  else
898  {
899  ::std::swap(nMiddle, nLast);
900  nLast = nLast + nMiddle - nFirst;
901  }
902 
903  if( !(nFirst < nParas && nMiddle < nParas && nLast < nParas) )
904  return;
905 
906  // since we have no "paragraph index
907  // changed" event on UAA, remove
908  // [first,last] and insert again later (in
909  // UpdateVisibleChildren)
910 
911  // maParaManager.Rotate( nFirst, nMiddle, nLast );
912 
913  // send CHILD_EVENT to affected children
914  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
915  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
916 
917  ::std::advance( begin, nFirst );
918  ::std::advance( end, nLast+1 );
919 
920  // TODO: maybe optimize here in the following way. If the
921  // number of removed children exceeds a certain threshold,
922  // use InvalidateFlags::Children
923  AccessibleTextHelper_LostChildEvent aFunctor( *this );
924 
925  ::std::for_each( begin, end, aFunctor );
926 
927  maParaManager.Release(nFirst, nLast+1);
928  // should be no need for UpdateBoundRect, since all affected children are cleared.
929  }
930 
931  namespace {
932 
933  // functor for sending child events (no stand-alone function, they are maybe not inlined)
934  class AccessibleTextHelper_ChildrenTextChanged
935  {
936  public:
937  void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
938  {
939  rPara.TextChanged();
940  }
941  };
942 
948  class AccessibleTextHelper_QueueFunctor
949  {
950  public:
951  AccessibleTextHelper_QueueFunctor() :
952  mnParasChanged( 0 ),
953  mnParaIndex(-1),
955  {}
956  void operator()( const SfxHint* pEvent )
957  {
958  if( !pEvent || mnParasChanged == -1 )
959  return;
960 
961  // determine hint type
962  const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent );
963  const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent );
964 
965  if( !(!pEditSourceHint && pTextHint &&
966  (pTextHint->GetId() == SfxHintId::TextParaInserted ||
967  pTextHint->GetId() == SfxHintId::TextParaRemoved )) )
968  return;
969 
970  if( pTextHint->GetValue() == EE_PARA_ALL )
971  {
972  mnParasChanged = -1;
973  }
974  else
975  {
976  mnHintId = pTextHint->GetId();
977  mnParaIndex = pTextHint->GetValue();
978  ++mnParasChanged;
979  }
980  }
981 
987  sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; }
993  sal_Int32 GetParaIndex() const { return mnParaIndex; }
998  SfxHintId GetHintId() const { return mnHintId; }
999 
1000  private:
1004  sal_Int32 mnParasChanged;
1006  sal_Int32 mnParaIndex;
1009  };
1010 
1011  }
1012 
1014  {
1015  // inspect queue for paragraph insert/remove events. If there
1016  // is exactly _one_ of those in the queue, and the number of
1017  // paragraphs has changed by exactly one, use that event to
1018  // determine a priori which paragraph was added/removed. This
1019  // is necessary, since I must sync right here with the
1020  // EditEngine state (number of paragraphs etc.), since I'm
1021  // potentially sending listener events right away.
1022  AccessibleTextHelper_QueueFunctor aFunctor;
1023  maEventQueue.ForEach( aFunctor );
1024 
1025  const sal_Int32 nNewParas( GetTextForwarder().GetParagraphCount() );
1026  const sal_Int32 nCurrParas( maParaManager.GetNum() );
1027 
1028  // whether every paragraph already is updated (no need to
1029  // repeat that later on, e.g. for PARA_MOVED events)
1030  bool bEverythingUpdated( false );
1031 
1032  if( std::abs( nNewParas - nCurrParas ) == 1 &&
1033  aFunctor.GetNumberOfParasChanged() == 1 )
1034  {
1035  // #103483# Exactly one paragraph added/removed. This is
1036  // the normal case, optimize event handling here.
1037 
1038  if( aFunctor.GetHintId() == SfxHintId::TextParaInserted )
1039  {
1040  // update num of paras
1041  maParaManager.SetNum( nNewParas );
1042 
1043  // release everything from the insertion position until the end
1044  maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1045 
1046  // TODO: Clarify whether this behaviour _really_ saves
1047  // anybody anything!
1048  // update children, _don't_ broadcast
1049  UpdateVisibleChildren( false );
1050  UpdateBoundRect();
1051 
1052  // send insert event
1053  // #109864# Enforce creation of this paragraph
1054  try
1055  {
1056  GotPropertyEvent( uno::makeAny( getAccessibleChild( aFunctor.GetParaIndex() -
1058  AccessibleEventId::CHILD );
1059  }
1060  catch( const uno::Exception& )
1061  {
1062  OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph");
1063  }
1064  }
1065  else if( aFunctor.GetHintId() == SfxHintId::TextParaRemoved )
1066  {
1067  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
1068  ::std::advance( begin, aFunctor.GetParaIndex() );
1069  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
1070  ::std::advance( end, 1 );
1071 
1072  // #i61812# remember para to be removed for later notification
1073  // AFTER the new state is applied (that after the para got removed)
1074  ::uno::Reference< XAccessible > xPara(begin->first.get());
1075 
1076  // release everything from the remove position until the end
1077  maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1078 
1079  // update num of paras
1080  maParaManager.SetNum( nNewParas );
1081 
1082  // TODO: Clarify whether this behaviour _really_ saves
1083  // anybody anything!
1084  // update children, _don't_ broadcast
1085  UpdateVisibleChildren( false );
1086  UpdateBoundRect();
1087 
1088  // #i61812# notification for removed para
1089  if (xPara.is())
1090  FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny( xPara) );
1091  }
1092 #ifdef DBG_UTIL
1093  else
1094  OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue() invalid hint id");
1095 #endif
1096  }
1097  else if( nNewParas != nCurrParas )
1098  {
1099  // release all paras
1100  maParaManager.Release(0, nCurrParas);
1101 
1102  // update num of paras
1103  maParaManager.SetNum( nNewParas );
1104 
1105  // #109864# create from scratch, don't broadcast
1106  UpdateVisibleChildren( false );
1107  UpdateBoundRect();
1108 
1109  // number of paragraphs somehow changed - but we have no
1110  // chance determining how. Thus, throw away everything and
1111  // create from scratch.
1112  // (child events should be broadcast after the changes are done...)
1113  FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
1114 
1115  // no need for further updates later on
1116  bEverythingUpdated = true;
1117  }
1118 
1119  while( !maEventQueue.IsEmpty() )
1120  {
1121  ::std::unique_ptr< SfxHint > pHint( maEventQueue.PopFront() );
1122  if (pHint)
1123  {
1124  const SfxHint& rHint = *pHint;
1125 
1126  // Note, if you add events here, you need to update the AccessibleTextEventQueue::Append
1127  // code, because only the events we process here, are actually queued there.
1128 
1129  try
1130  {
1131 
1132  if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1133  {
1134  const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1135 
1136  switch( pSdrHint->GetKind() )
1137  {
1139  {
1140  if(!IsActive())
1141  {
1142  break;
1143  }
1144  // change children state
1145  maParaManager.SetActive();
1146 
1147  // per definition, edit mode text has the focus
1148  SetFocus( true );
1149  break;
1150  }
1151 
1152  case SdrHintKind::EndEdit:
1153  {
1154  // focused child now loses focus
1155  ESelection aSelection;
1156  if( GetEditViewForwarder().GetSelection( aSelection ) )
1157  SetChildFocus( aSelection.nEndPara, false );
1158 
1159  // change children state
1160  maParaManager.SetActive( false );
1161 
1164  break;
1165  }
1166  default:
1167  break;
1168  }
1169  }
1170  else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1171  {
1172  switch( pEditSourceHint->GetId() )
1173  {
1174  case SfxHintId::EditSourceParasMoved:
1175  {
1176  DBG_ASSERT( pEditSourceHint->GetStartValue() < GetTextForwarder().GetParagraphCount() &&
1177  pEditSourceHint->GetEndValue() < GetTextForwarder().GetParagraphCount(),
1178  "AccessibleTextHelper_Impl::NotifyHdl: Invalid notification");
1179 
1180  if( !bEverythingUpdated )
1181  {
1182  ParagraphsMoved(pEditSourceHint->GetStartValue(),
1183  pEditSourceHint->GetValue(),
1184  pEditSourceHint->GetEndValue());
1185 
1186  // in all cases, check visibility afterwards.
1188  }
1189  break;
1190  }
1191 
1192  case SfxHintId::EditSourceSelectionChanged:
1193  // notify listeners
1194  try
1195  {
1196  UpdateSelection();
1197  }
1198  // maybe we're not in edit mode (this is not an error)
1199  catch( const uno::Exception& ) {}
1200  break;
1201  default: break;
1202  }
1203  }
1204  else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1205  {
1206  const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
1207 
1208  switch( pTextHint->GetId() )
1209  {
1210  case SfxHintId::TextModified:
1211  {
1212  // notify listeners
1213  sal_Int32 nPara( pTextHint->GetValue() );
1214 
1215  // #108900# Delegate change event to children
1216  AccessibleTextHelper_ChildrenTextChanged aNotifyChildrenFunctor;
1217 
1218  if( nPara == EE_PARA_ALL )
1219  {
1220  // #108900# Call every child
1221  ::std::for_each( maParaManager.begin(), maParaManager.end(),
1222  AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1223  }
1224  else
1225  if( nPara < nParas )
1226  {
1227  // #108900# Call child at index nPara
1228  ::std::for_each( maParaManager.begin()+nPara, maParaManager.begin()+nPara+1,
1229  AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1230  }
1231  break;
1232  }
1233 
1234  case SfxHintId::TextParaInserted:
1235  // already happened above
1236  break;
1237 
1238  case SfxHintId::TextParaRemoved:
1239  // already happened above
1240  break;
1241 
1242  case SfxHintId::TextHeightChanged:
1243  // visibility changed, done below
1244  break;
1245 
1246  case SfxHintId::TextViewScrolled:
1247  // visibility changed, done below
1248  break;
1249  default: break;
1250  }
1251 
1252  // in all cases, check visibility afterwards.
1254  UpdateBoundRect();
1255  }
1256  else if ( dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1257  {
1258  // just check visibility
1260  UpdateBoundRect();
1261  }
1262  // it's VITAL to keep the SfxSimpleHint last! It's the base of some classes above!
1263  else if( rHint.GetId() == SfxHintId::Dying)
1264  {
1265  // edit source is dying under us, become defunc then
1266  try
1267  {
1268  // make edit source inaccessible
1269  // Note: cannot destroy it here, since we're called from there!
1271  }
1272  catch( const uno::Exception& ) {}
1273  }
1274  }
1275  catch( const uno::Exception& )
1276  {
1277  DBG_UNHANDLED_EXCEPTION("svx");
1278  }
1279  }
1280  }
1281  }
1282 
1284  {
1285  // precondition: solar mutex locked
1287 
1288  // precondition: not in a recursion
1289  if( mbInNotify )
1290  return;
1291 
1292  mbInNotify = true;
1293 
1294  try
1295  {
1296  // Process notification event, arranged in order of likelihood of
1297  // occurrence to avoid unnecessary dynamic_cast. Note that
1298  // SvxEditSourceHint is derived from TextHint, so has to be checked
1299  // before that.
1300  if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1301  {
1302  const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1303  // process drawing layer events right away, if not
1304  // within an open EE notification frame. Otherwise,
1305  // event processing would be delayed until next EE
1306  // notification sequence.
1307  maEventQueue.Append( *pSdrHint );
1308  }
1309  else if( const SvxViewChangedHint* pViewHint = dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1310  {
1311  // process visibility right away, if not within an
1312  // open EE notification frame. Otherwise, event
1313  // processing would be delayed until next EE
1314  // notification sequence.
1315  maEventQueue.Append( *pViewHint );
1316  }
1317  else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1318  {
1319  // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1320  maEventQueue.Append( *pEditSourceHint );
1321  }
1322  else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1323  {
1324  // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1325  if(pTextHint->GetId() == SfxHintId::TextProcessNotifications)
1326  ProcessQueue();
1327  else
1328  maEventQueue.Append( *pTextHint );
1329  }
1330  // it's VITAL to keep the SfxHint last! It's the base of the classes above!
1331  else if( rHint.GetId() == SfxHintId::Dying )
1332  {
1333  // handle this event _at once_, because after that, objects are invalid
1334  // edit source is dying under us, become defunc then
1335  maEventQueue.Clear();
1336  try
1337  {
1338  // make edit source inaccessible
1339  // Note: cannot destroy it here, since we're called from there!
1341  }
1342  catch( const uno::Exception& ) {}
1343  }
1344  }
1345  catch( const uno::Exception& )
1346  {
1347  DBG_UNHANDLED_EXCEPTION("svx");
1348  mbInNotify = false;
1349  }
1350 
1351  mbInNotify = false;
1352  }
1353 
1355  {
1356  if( getNotifierClientId() != -1 )
1357  {
1358  try
1359  {
1360  // #106234# Unregister from EventNotifier
1362  SAL_INFO("svx", "disposed ID: " << mnNotifierClientId );
1363  }
1364  catch( const uno::Exception& ) {}
1365 
1366  mnNotifierClientId = -1;
1367  }
1368 
1369  try
1370  {
1371  // dispose children
1372  maParaManager.Dispose();
1373  }
1374  catch( const uno::Exception& ) {}
1375 
1376  // quit listen on stale edit source
1377  if( maEditSource.IsValid() )
1378  EndListening( maEditSource.GetBroadcaster() );
1379 
1380  // clear references
1381  maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
1382  mxFrontEnd = nullptr;
1383  }
1384 
1385  void AccessibleTextHelper_Impl::FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const
1386  {
1387  // -- object locked --
1388  AccessibleEventObject aEvent;
1389  {
1390  std::scoped_lock aGuard(maMutex);
1391 
1392  DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper::FireEvent: no event source set");
1393 
1394  if (mxFrontEnd.is())
1395  aEvent = AccessibleEventObject(mxFrontEnd->getAccessibleContext(), nEventId,
1396  rNewValue, rOldValue);
1397  else
1398  aEvent = AccessibleEventObject(uno::Reference<uno::XInterface>(), nEventId,
1399  rNewValue, rOldValue);
1400 
1401  // no locking necessary, FireEvent internally copies listeners
1402  // if someone removes/adds in between Further locking,
1403  // actually, might lead to deadlocks, since we're calling out
1404  // of this object
1405  }
1406  // -- until here --
1407 
1408  FireEvent(aEvent);
1409  }
1410 
1411  void AccessibleTextHelper_Impl::FireEvent( const AccessibleEventObject& rEvent ) const
1412  {
1413  // #102261# Call global queue for focus events
1414  if( rEvent.EventId == AccessibleStateType::FOCUSED )
1416 
1417  // #106234# Delegate to EventNotifier
1419  rEvent );
1420  }
1421 
1422  // XAccessibleContext
1424  {
1426  }
1427 
1428  uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int32 i )
1429  {
1430  i -= GetStartIndex();
1431 
1432  if( 0 > i || i >= getAccessibleChildCount() ||
1433  GetTextForwarder().GetParagraphCount() <= i )
1434  {
1435  throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd);
1436  }
1437 
1438  DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set");
1439 
1440  if( mxFrontEnd.is() )
1441  return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first;
1442  else
1443  return nullptr;
1444  }
1445 
1446  void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1447  {
1448  if( getNotifierClientId() != -1 )
1450  }
1451 
1452  void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1453  {
1454  if( getNotifierClientId() == -1 )
1455  return;
1456 
1457  const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener );
1458  if ( !nListenerCount )
1459  {
1460  // no listeners anymore
1461  // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client),
1462  // and at least to us not firing any events anymore, in case somebody calls
1463  // NotifyAccessibleEvent, again
1465  mnNotifierClientId = -1;
1467  }
1468  }
1469 
1470  uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint )
1471  {
1472  // make given position relative
1473  if( !mxFrontEnd.is() )
1474  throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1475 
1476  uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext();
1477 
1478  if( !xFrontEndContext.is() )
1479  throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1480 
1481  uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW );
1482 
1483  // #103862# No longer need to make given position relative
1484  Point aPoint( _aPoint.X, _aPoint.Y );
1485 
1486  // respect EditEngine offset to surrounding shape/cell
1487  aPoint -= GetOffset();
1488 
1489  // convert to EditEngine coordinate system
1490  SvxTextForwarder& rCacheTF = GetTextForwarder();
1491  Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) );
1492 
1493  // iterate over all visible children (including those not yet created)
1494  sal_Int32 nChild;
1495  for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild )
1496  {
1497  DBG_ASSERT(nChild >= 0,
1498  "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow");
1499 
1500  tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) );
1501 
1502  if( aParaBounds.Contains( aLogPoint ) )
1503  return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() );
1504  }
1505 
1506  // found none
1507  return nullptr;
1508  }
1509 
1510 
1511  // AccessibleTextHelper implementation (simply forwards to impl)
1512 
1513  AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) :
1514  mpImpl( new AccessibleTextHelper_Impl() )
1515  {
1516  SolarMutexGuard aGuard;
1517 
1518  SetEditSource( std::move(pEditSource) );
1519  }
1520 
1522  {
1523  }
1524 
1526  {
1527 #ifdef DBG_UTIL
1528  mpImpl->CheckInvariants();
1529 
1530  const SvxEditSource& aEditSource = mpImpl->GetEditSource();
1531 
1532  mpImpl->CheckInvariants();
1533 
1534  return aEditSource;
1535 #else
1536  return mpImpl->GetEditSource();
1537 #endif
1538  }
1539 
1540  void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
1541  {
1542 #ifdef DBG_UTIL
1543  // precondition: solar mutex locked
1545 
1546  mpImpl->CheckInvariants();
1547 #endif
1548 
1549  mpImpl->SetEditSource( std::move(pEditSource) );
1550 
1551 #ifdef DBG_UTIL
1552  mpImpl->CheckInvariants();
1553 #endif
1554  }
1555 
1556  void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface )
1557  {
1558 #ifdef DBG_UTIL
1559  mpImpl->CheckInvariants();
1560 #endif
1561 
1562  mpImpl->SetEventSource( rInterface );
1563 
1564 #ifdef DBG_UTIL
1565  mpImpl->CheckInvariants();
1566 #endif
1567  }
1568 
1569  void AccessibleTextHelper::SetFocus( bool bHaveFocus )
1570  {
1571 #ifdef DBG_UTIL
1572  // precondition: solar mutex locked
1574 
1575  mpImpl->CheckInvariants();
1576 #endif
1577 
1578  mpImpl->SetFocus( bHaveFocus );
1579 
1580 #ifdef DBG_UTIL
1581  mpImpl->CheckInvariants();
1582 #endif
1583  }
1584 
1586  {
1587 #ifdef DBG_UTIL
1588  mpImpl->CheckInvariants();
1589 
1590  bool bRet( mpImpl->HaveFocus() );
1591 
1592  mpImpl->CheckInvariants();
1593 
1594  return bRet;
1595 #else
1596  return mpImpl->HaveFocus();
1597 #endif
1598  }
1599 
1600  void AccessibleTextHelper::SetOffset( const Point& rPoint )
1601  {
1602 #ifdef DBG_UTIL
1603  // precondition: solar mutex locked
1605 
1606  mpImpl->CheckInvariants();
1607 #endif
1608 
1609  mpImpl->SetOffset( rPoint );
1610 
1611 #ifdef DBG_UTIL
1612  mpImpl->CheckInvariants();
1613 #endif
1614  }
1615 
1616  void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset )
1617  {
1618 #ifdef DBG_UTIL
1619  // precondition: solar mutex locked
1621 
1622  mpImpl->CheckInvariants();
1623 #endif
1624 
1625  mpImpl->SetStartIndex( nOffset );
1626 
1627 #ifdef DBG_UTIL
1628  mpImpl->CheckInvariants();
1629 #endif
1630  }
1631 
1633  {
1634 #ifdef DBG_UTIL
1635  mpImpl->CheckInvariants();
1636 
1637  sal_Int32 nOffset = mpImpl->GetStartIndex();
1638 
1639  mpImpl->CheckInvariants();
1640 
1641  return nOffset;
1642 #else
1643  return mpImpl->GetStartIndex();
1644 #endif
1645  }
1646 
1647  void AccessibleTextHelper::SetAdditionalChildStates( VectorOfStates&& rChildStates )
1648  {
1649  mpImpl->SetAdditionalChildStates( std::move(rChildStates) );
1650  }
1651 
1653  {
1654 #ifdef DBG_UTIL
1655  // precondition: solar mutex locked
1657 
1658  mpImpl->CheckInvariants();
1659 #endif
1660 
1661  mpImpl->UpdateVisibleChildren();
1662  mpImpl->UpdateBoundRect();
1663 
1664  mpImpl->UpdateSelection();
1665 
1666 #ifdef DBG_UTIL
1667  mpImpl->CheckInvariants();
1668 #endif
1669  }
1670 
1672  {
1673  // As Dispose calls ShutdownEditSource, which in turn
1674  // deregisters as listener on the edit source, have to lock
1675  // here
1676  SolarMutexGuard aGuard;
1677 
1678 #ifdef DBG_UTIL
1679  mpImpl->CheckInvariants();
1680 #endif
1681 
1682  mpImpl->Dispose();
1683 
1684 #ifdef DBG_UTIL
1685  mpImpl->CheckInvariants();
1686 #endif
1687  }
1688 
1689  // XAccessibleContext
1691  {
1692  SolarMutexGuard aGuard;
1693 
1694 #ifdef DBG_UTIL
1695  mpImpl->CheckInvariants();
1696 
1697  sal_Int32 nRet = mpImpl->getAccessibleChildCount();
1698 
1699  mpImpl->CheckInvariants();
1700 
1701  return nRet;
1702 #else
1703  return mpImpl->getAccessibleChildCount();
1704 #endif
1705  }
1706 
1707  uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int32 i )
1708  {
1709  SolarMutexGuard aGuard;
1710 
1711 #ifdef DBG_UTIL
1712  mpImpl->CheckInvariants();
1713 
1714  uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i );
1715 
1716  mpImpl->CheckInvariants();
1717 
1718  return xRet;
1719 #else
1720  return mpImpl->getAccessibleChild( i );
1721 #endif
1722  }
1723 
1724  void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1725  {
1726 #ifdef DBG_UTIL
1727  mpImpl->CheckInvariants();
1728 
1729  mpImpl->addAccessibleEventListener( xListener );
1730 
1731  mpImpl->CheckInvariants();
1732 #else
1733  mpImpl->addAccessibleEventListener( xListener );
1734 #endif
1735  }
1736 
1737  void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1738  {
1739 #ifdef DBG_UTIL
1740  mpImpl->CheckInvariants();
1741 
1742  mpImpl->removeAccessibleEventListener( xListener );
1743 
1744  mpImpl->CheckInvariants();
1745 #else
1746  mpImpl->removeAccessibleEventListener( xListener );
1747 #endif
1748  }
1749 
1750  // XAccessibleComponent
1751  uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint )
1752  {
1753  SolarMutexGuard aGuard;
1754 
1755 #ifdef DBG_UTIL
1756  mpImpl->CheckInvariants();
1757 
1758  uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint );
1759 
1760  mpImpl->CheckInvariants();
1761 
1762  return xChild;
1763 #else
1764  return mpImpl->getAccessibleAtPoint( aPoint );
1765 #endif
1766  }
1767 
1768 } // end of namespace accessibility
1769 
1770 
1771 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void Adjust()
SfxHintId
sal_Int32 nStartPara
int mnNotifierClientId
client Id from AccessibleEventNotifier
virtual tools::Rectangle GetParaBounds(sal_Int32 nPara) const =0
uno::Reference< XAccessible > getAccessibleAtPoint(const awt::Point &aPoint)
virtual bool IsValid() const =0
SdrHintKind GetKind() const
Definition: svdmodel.hxx:132
void UpdateVisibleChildren(bool bBroadcastEvents=true)
void GetSelection(struct ESelection &rSel, SvxTextForwarder const *pForwarder) noexcept
const SvxEditSource & GetEditSource() const
Query the current edit source.
SfxHintId mnHintId
TextHint ID (removed/inserted) of last interesting event.
virtual SvxEditViewForwarder * GetEditViewForwarder(bool bCreate=false)
sal_Int32 GetValue() const
css::uno::Reference< css::accessibility::XAccessible > GetAt(const css::awt::Point &aPoint)
Implements getAccessibleAt.
void GotPropertyEvent(const uno::Any &rNewValue, const sal_Int16 nEventId) const
#define EE_INDEX_NOT_FOUND
sal_Int16 nId
SvxEditSourceAdapter & GetEditSource() const
const std::unique_ptr< AccessibleTextHelper_Impl > mpImpl
SfxHintId GetId() const
void Dispose()
Drop all references and enter disposed state.
NONE
void SetOffset(const Point &rPoint)
Set offset of EditEngine/Outliner from parent.
#define EE_PARA_NOT_FOUND
sal_Int32 mnParaIndex
index of paragraph added/removed last
void FireEvent(const sal_Int16 nEventId, const uno::Any &rNewValue=uno::Any(), const uno::Any &rOldValue=uno::Any()) const
void SetEditSource(::std::unique_ptr< SvxEditSource > &&pEditSource)
Set the current edit source.
enumrange< T >::Iterator begin(enumrange< T >)
static::std::pair< first_type, second_type > makeSortedPair(first_type first, second_type second)
sal_Int32 GetChildCount() const
Implements getAccessibleChildCount.
sal_Int32 mnStartIndex
void SetAdditionalChildStates(VectorOfStates &&rChildStates)
Sets a vector of additional accessible states.
sal_Int32 nEndPos
const sal_Int32 mnDifference
SvxEditViewForwarder & GetEditViewForwarder() const
void addAccessibleEventListener(const uno::Reference< XAccessibleEventListener > &xListener)
#define DBG_UNHANDLED_EXCEPTION(...)
sal_Int32 nEndPara
#define DBG_ASSERT(sCon, aError)
#define EE_PARA_ALL
void RemoveEventListener(const css::uno::Reference< css::accessibility::XAccessibleEventListener > &xListener)
Implements removeEventListener.
void Append(const SdrHint &rHint)
Append event to end of queue.
virtual MapMode GetMapMode() const =0
::std::unique_ptr< SfxHint > PopFront()
Pop first queue element.
void SetEventSource(const css::uno::Reference< css::accessibility::XAccessible > &rInterface)
Set the event source.
static sal_Int32 removeEventListener(const TClientId _nClient, const css::uno::Reference< css::accessibility::XAccessibleEventListener > &_rxListener)
void StartListening(SfxBroadcaster &rBroadcaster, DuplicateHandling eDuplicateHanding=DuplicateHandling::Unexpected)
void SetChildFocus(sal_Int32 nChild, bool bHaveFocus)
const sdr::table::CellRef & getCellRef() const
void AddEventListener(const css::uno::Reference< css::accessibility::XAccessibleEventListener > &xListener)
Implements addEventListener.
AccessibleTextHelper_Impl & mrImpl
enumrange< T >::Iterator end(enumrange< T >)
AccessibleTextHelper(::std::unique_ptr< SvxEditSource > &&pEditSource)
Create accessible text object for given edit source.
uno::Reference< XAccessible > getAccessibleChild(sal_Int32 i)
sal_Int32 GetStartIndex() const
Query offset the object adds to all children's indices.
void UpdateChildren()
Update the visible children.
bool HaveFocus()
Query the focus state of the surrounding object.
bool HasRange() const
Point maOffset
our current offset to the containing shape/cell (guarded by maMutex)
void SetEditSource(::std::unique_ptr< SvxEditSource > &&pEditSource)
static void revokeClient(const TClientId _nClient)
#define SAL_INFO(area, stream)
void EndListening(SfxBroadcaster &rBroadcaster, bool bRemoveAllDuplicates=false)
sal_Int32 GetEndValue() const
virtual bool IsValid() const =0
::accessibility::AccessibleParaManager maParaManager
void ForEach(Functor &rFunctor) const
Apply functor to every queue member.
void SetAdditionalChildStates(VectorOfStates &&rChildStates)
#define DBG_TESTSOLARMUTEX()
This class handles the notification events for the AccessibleTextHelper class.
static void addEvent(const TClientId _nClient, const css::accessibility::AccessibleEventObject &_rEvent)
sal_Int32 GetStartValue() const
sal_Int32 mnParasChanged
number of paragraphs changed during queue processing.
bool IsEmpty() const
Query whether queue is empty.
void ParagraphsMoved(sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast)
void SetFocus(bool bHaveFocus=true)
Set the focus state of the accessibility object.
void SetStartIndex(sal_Int32 nOffset)
Set offset the object adds to all children's indices.
virtual sal_Int32 GetParagraphCount() const =0
virtual void Notify(SfxBroadcaster &rBC, const SfxHint &rHint) override
void SetEventSource(const uno::Reference< XAccessible > &rInterface)
VCL_DLLPUBLIC void NotifyAccessibleStateEventGlobally(const css::accessibility::AccessibleEventObject &rEventObject)
AnyEventRef aEvent
void removeAccessibleEventListener(const uno::Reference< XAccessibleEventListener > &xListener)
css::uno::Reference< css::accessibility::XAccessible > GetChild(sal_Int32 i)
Implements getAccessibleChild.
static sal_Int32 addEventListener(const TClientId _nClient, const css::uno::Reference< css::accessibility::XAccessibleEventListener > &_rxListener)
AccessibleTableShape * GetParentTable()
sal_Int32 nStartPos