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::Any(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::Any(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::Any(static_cast<sal_Int32>(-1)),
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::Any(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::Any( 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::Any(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::Any( 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::Any( 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  // #106234# Delegate to EventNotifier
1415  rEvent );
1416  }
1417 
1418  // XAccessibleContext
1420  {
1422  }
1423 
1424  uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int32 i )
1425  {
1426  i -= GetStartIndex();
1427 
1428  if( 0 > i || i >= getAccessibleChildCount() ||
1429  GetTextForwarder().GetParagraphCount() <= i )
1430  {
1431  throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd);
1432  }
1433 
1434  DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set");
1435 
1436  if( mxFrontEnd.is() )
1437  return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first;
1438  else
1439  return nullptr;
1440  }
1441 
1442  void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1443  {
1444  if( getNotifierClientId() != -1 )
1446  }
1447 
1448  void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1449  {
1450  if( getNotifierClientId() == -1 )
1451  return;
1452 
1453  const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener );
1454  if ( !nListenerCount )
1455  {
1456  // no listeners anymore
1457  // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client),
1458  // and at least to us not firing any events anymore, in case somebody calls
1459  // NotifyAccessibleEvent, again
1461  mnNotifierClientId = -1;
1463  }
1464  }
1465 
1466  uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint )
1467  {
1468  // make given position relative
1469  if( !mxFrontEnd.is() )
1470  throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1471 
1472  uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext();
1473 
1474  if( !xFrontEndContext.is() )
1475  throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1476 
1477  uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW );
1478 
1479  // #103862# No longer need to make given position relative
1480  Point aPoint( _aPoint.X, _aPoint.Y );
1481 
1482  // respect EditEngine offset to surrounding shape/cell
1483  aPoint -= GetOffset();
1484 
1485  // convert to EditEngine coordinate system
1486  SvxTextForwarder& rCacheTF = GetTextForwarder();
1487  Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) );
1488 
1489  // iterate over all visible children (including those not yet created)
1490  sal_Int32 nChild;
1491  for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild )
1492  {
1493  DBG_ASSERT(nChild >= 0,
1494  "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow");
1495 
1496  tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) );
1497 
1498  if( aParaBounds.Contains( aLogPoint ) )
1499  return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() );
1500  }
1501 
1502  // found none
1503  return nullptr;
1504  }
1505 
1506 
1507  // AccessibleTextHelper implementation (simply forwards to impl)
1508 
1509  AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) :
1510  mpImpl( new AccessibleTextHelper_Impl() )
1511  {
1512  SolarMutexGuard aGuard;
1513 
1514  SetEditSource( std::move(pEditSource) );
1515  }
1516 
1518  {
1519  }
1520 
1522  {
1523 #ifdef DBG_UTIL
1524  mpImpl->CheckInvariants();
1525 
1526  const SvxEditSource& aEditSource = mpImpl->GetEditSource();
1527 
1528  mpImpl->CheckInvariants();
1529 
1530  return aEditSource;
1531 #else
1532  return mpImpl->GetEditSource();
1533 #endif
1534  }
1535 
1536  void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
1537  {
1538 #ifdef DBG_UTIL
1539  // precondition: solar mutex locked
1541 
1542  mpImpl->CheckInvariants();
1543 #endif
1544 
1545  mpImpl->SetEditSource( std::move(pEditSource) );
1546 
1547 #ifdef DBG_UTIL
1548  mpImpl->CheckInvariants();
1549 #endif
1550  }
1551 
1552  void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface )
1553  {
1554 #ifdef DBG_UTIL
1555  mpImpl->CheckInvariants();
1556 #endif
1557 
1558  mpImpl->SetEventSource( rInterface );
1559 
1560 #ifdef DBG_UTIL
1561  mpImpl->CheckInvariants();
1562 #endif
1563  }
1564 
1565  void AccessibleTextHelper::SetFocus( bool bHaveFocus )
1566  {
1567 #ifdef DBG_UTIL
1568  // precondition: solar mutex locked
1570 
1571  mpImpl->CheckInvariants();
1572 #endif
1573 
1574  mpImpl->SetFocus( bHaveFocus );
1575 
1576 #ifdef DBG_UTIL
1577  mpImpl->CheckInvariants();
1578 #endif
1579  }
1580 
1582  {
1583 #ifdef DBG_UTIL
1584  mpImpl->CheckInvariants();
1585 
1586  bool bRet( mpImpl->HaveFocus() );
1587 
1588  mpImpl->CheckInvariants();
1589 
1590  return bRet;
1591 #else
1592  return mpImpl->HaveFocus();
1593 #endif
1594  }
1595 
1596  void AccessibleTextHelper::SetOffset( const Point& rPoint )
1597  {
1598 #ifdef DBG_UTIL
1599  // precondition: solar mutex locked
1601 
1602  mpImpl->CheckInvariants();
1603 #endif
1604 
1605  mpImpl->SetOffset( rPoint );
1606 
1607 #ifdef DBG_UTIL
1608  mpImpl->CheckInvariants();
1609 #endif
1610  }
1611 
1612  void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset )
1613  {
1614 #ifdef DBG_UTIL
1615  // precondition: solar mutex locked
1617 
1618  mpImpl->CheckInvariants();
1619 #endif
1620 
1621  mpImpl->SetStartIndex( nOffset );
1622 
1623 #ifdef DBG_UTIL
1624  mpImpl->CheckInvariants();
1625 #endif
1626  }
1627 
1629  {
1630 #ifdef DBG_UTIL
1631  mpImpl->CheckInvariants();
1632 
1633  sal_Int32 nOffset = mpImpl->GetStartIndex();
1634 
1635  mpImpl->CheckInvariants();
1636 
1637  return nOffset;
1638 #else
1639  return mpImpl->GetStartIndex();
1640 #endif
1641  }
1642 
1643  void AccessibleTextHelper::SetAdditionalChildStates( VectorOfStates&& rChildStates )
1644  {
1645  mpImpl->SetAdditionalChildStates( std::move(rChildStates) );
1646  }
1647 
1649  {
1650 #ifdef DBG_UTIL
1651  // precondition: solar mutex locked
1653 
1654  mpImpl->CheckInvariants();
1655 #endif
1656 
1657  mpImpl->UpdateVisibleChildren();
1658  mpImpl->UpdateBoundRect();
1659 
1660  mpImpl->UpdateSelection();
1661 
1662 #ifdef DBG_UTIL
1663  mpImpl->CheckInvariants();
1664 #endif
1665  }
1666 
1668  {
1669  // As Dispose calls ShutdownEditSource, which in turn
1670  // deregisters as listener on the edit source, have to lock
1671  // here
1672  SolarMutexGuard aGuard;
1673 
1674 #ifdef DBG_UTIL
1675  mpImpl->CheckInvariants();
1676 #endif
1677 
1678  mpImpl->Dispose();
1679 
1680 #ifdef DBG_UTIL
1681  mpImpl->CheckInvariants();
1682 #endif
1683  }
1684 
1685  // XAccessibleContext
1687  {
1688  SolarMutexGuard aGuard;
1689 
1690 #ifdef DBG_UTIL
1691  mpImpl->CheckInvariants();
1692 
1693  sal_Int32 nRet = mpImpl->getAccessibleChildCount();
1694 
1695  mpImpl->CheckInvariants();
1696 
1697  return nRet;
1698 #else
1699  return mpImpl->getAccessibleChildCount();
1700 #endif
1701  }
1702 
1703  uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int32 i )
1704  {
1705  SolarMutexGuard aGuard;
1706 
1707 #ifdef DBG_UTIL
1708  mpImpl->CheckInvariants();
1709 
1710  uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i );
1711 
1712  mpImpl->CheckInvariants();
1713 
1714  return xRet;
1715 #else
1716  return mpImpl->getAccessibleChild( i );
1717 #endif
1718  }
1719 
1720  void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1721  {
1722 #ifdef DBG_UTIL
1723  mpImpl->CheckInvariants();
1724 
1725  mpImpl->addAccessibleEventListener( xListener );
1726 
1727  mpImpl->CheckInvariants();
1728 #else
1729  mpImpl->addAccessibleEventListener( xListener );
1730 #endif
1731  }
1732 
1733  void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1734  {
1735 #ifdef DBG_UTIL
1736  mpImpl->CheckInvariants();
1737 
1738  mpImpl->removeAccessibleEventListener( xListener );
1739 
1740  mpImpl->CheckInvariants();
1741 #else
1742  mpImpl->removeAccessibleEventListener( xListener );
1743 #endif
1744  }
1745 
1746  // XAccessibleComponent
1747  uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint )
1748  {
1749  SolarMutexGuard aGuard;
1750 
1751 #ifdef DBG_UTIL
1752  mpImpl->CheckInvariants();
1753 
1754  uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint );
1755 
1756  mpImpl->CheckInvariants();
1757 
1758  return xChild;
1759 #else
1760  return mpImpl->getAccessibleAtPoint( aPoint );
1761 #endif
1762  }
1763 
1764 } // end of namespace accessibility
1765 
1766 
1767 /* 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)
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)
bool m_bDetectedRangeSegmentation false
AccessibleTableShape * GetParentTable()
sal_Int32 nStartPos