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 <utility>
25 #include <algorithm>
26 #include <osl/mutex.hxx>
27 #include <sal/log.hxx>
28 #include <com/sun/star/uno/Any.hxx>
29 #include <com/sun/star/uno/Reference.hxx>
30 #include <com/sun/star/awt/Point.hpp>
31 #include <com/sun/star/awt/Rectangle.hpp>
32 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
33 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
34 #include <com/sun/star/accessibility/XAccessible.hpp>
35 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
36 #include <com/sun/star/accessibility/XAccessibleComponent.hpp>
37 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
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  ::osl::MutexGuard 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( const VectorOfStates& rChildStates );
126 
127  void Dispose();
128 
129  // do NOT hold object mutex when calling this! Danger of deadlock
130  void FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue = uno::Any(), const uno::Any& rOldValue = uno::Any() ) const;
131  void FireEvent( const AccessibleEventObject& rEvent ) const;
132 
133  void SetFocus( bool bHaveFocus );
134  bool HaveFocus()
135  {
136  // No locking of solar mutex here, since we rely on the fact
137  // that sal_Bool access is atomic
138  return mbThisHasFocus;
139  }
140  void SetChildFocus( sal_Int32 nChild, bool bHaveFocus );
141  void SetShapeFocus( bool bHaveFocus );
142  void ChangeChildFocus( sal_Int32 nNewChild );
143 
144 #ifdef DBG_UTIL
145  void CheckInvariants() const;
146 #endif
147 
148  // checks all children for visibility, throws away invisible ones
149  void UpdateVisibleChildren( bool bBroadcastEvents=true );
150 
151  // check all children for changes in position and size
152  void UpdateBoundRect();
153 
154  // calls SetSelection on the forwarder and updates maLastSelection
155  // cache.
156  void UpdateSelection();
157 
158  private:
159 
160  // Process event queue
161  void ProcessQueue();
162 
163  // syntactic sugar for FireEvent
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 ::osl::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( const VectorOfStates& rChildStates )
354  {
355  maParaManager.SetAdditionalChildStates( 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  ::osl::MutexGuard 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  mnFirstVisibleChild = -1;
754  mnLastVisibleChild = -2;
755 
756  for( sal_Int32 nCurrPara=0; nCurrPara<nParas; ++nCurrPara )
757  {
758  if (nCurrPara == 0)
759  mnFirstVisibleChild = nCurrPara;
760  mnLastVisibleChild = nCurrPara;
761  if (mxFrontEnd.is() && bBroadcastEvents)
762  {
763  // child not yet created?
764  ::accessibility::AccessibleParaManager::WeakChild aChild( maParaManager.GetChild(nCurrPara) );
765  if( aChild.second.Width == 0 &&
766  aChild.second.Height == 0 )
767  {
768  GotPropertyEvent( uno::makeAny( maParaManager.CreateChild( nCurrPara - mnFirstVisibleChild,
769  mxFrontEnd, GetEditSource(), nCurrPara ).first ),
770  AccessibleEventId::CHILD );
771  }
772  }
773  }
774  }
775  catch( const uno::Exception& )
776  {
777  OSL_FAIL("AccessibleTextHelper_Impl::UpdateVisibleChildren error while determining visible children");
778 
779  // something failed - currently no children
780  mnFirstVisibleChild = -1;
781  mnLastVisibleChild = -2;
782  maParaManager.SetNum(0);
783 
784  // lost all children
785  if( bBroadcastEvents )
786  FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
787  }
788  }
789 
790  namespace {
791 
792  // functor for checking changes in paragraph bounding boxes (no stand-alone function, maybe not inlined)
793  class AccessibleTextHelper_UpdateChildBounds
794  {
795  public:
796  explicit AccessibleTextHelper_UpdateChildBounds() {}
797  ::accessibility::AccessibleParaManager::WeakChild operator()( const ::accessibility::AccessibleParaManager::WeakChild& rChild )
798  {
799  // retrieve hard reference from weak one
800  auto aHardRef( rChild.first.get() );
801 
802  if( aHardRef.is() )
803  {
804  awt::Rectangle aNewRect = aHardRef->getBounds();
805  const awt::Rectangle& aOldRect = rChild.second;
806 
807  if( aNewRect.X != aOldRect.X ||
808  aNewRect.Y != aOldRect.Y ||
809  aNewRect.Width != aOldRect.Width ||
810  aNewRect.Height != aOldRect.Height )
811  {
812  // visible data changed
813  aHardRef->FireEvent( AccessibleEventId::BOUNDRECT_CHANGED );
814 
815  // update internal bounds
816  return ::accessibility::AccessibleParaManager::WeakChild( rChild.first, aNewRect );
817  }
818  }
819 
820  // identity transform
821  return rChild;
822  }
823  };
824 
825  }
826 
828  {
829  // send BOUNDRECT_CHANGED to affected children
830  AccessibleTextHelper_UpdateChildBounds aFunctor;
831  ::std::transform( maParaManager.begin(), maParaManager.end(), maParaManager.begin(), aFunctor );
832  }
833 
834 #ifdef DBG_UTIL
836  {
837  if( mnFirstVisibleChild >= 0 &&
839  {
840  OSL_FAIL( "AccessibleTextHelper: range invalid" );
841  }
842  }
843 #endif
844 
845  namespace {
846 
847  // functor for sending child events (no stand-alone function, they are maybe not inlined)
848  class AccessibleTextHelper_LostChildEvent
849  {
850  public:
851  explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {}
852  void operator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara )
853  {
854  // retrieve hard reference from weak one
855  auto aHardRef( rPara.first.get() );
856 
857  if( aHardRef.is() )
858  mrImpl.FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny<css::uno::Reference<css::accessibility::XAccessible>>(aHardRef.get()) );
859  }
860 
861  private:
862  AccessibleTextHelper_Impl& mrImpl;
863  };
864 
865  }
866 
867  void AccessibleTextHelper_Impl::ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast )
868  {
869  const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
870 
871  /* rotate paragraphs
872  * =================
873  *
874  * Three cases:
875  *
876  * 1.
877  * ... nParagraph ... nParam1 ... nParam2 ...
878  * |______________[xxxxxxxxxxx]
879  * becomes
880  * [xxxxxxxxxxx]|______________
881  *
882  * tail is 0
883  *
884  * 2.
885  * ... nParam1 ... nParagraph ... nParam2 ...
886  * [xxxxxxxxxxx|xxxxxxxxxxxxxx]____________
887  * becomes
888  * ____________[xxxxxxxxxxx|xxxxxxxxxxxxxx]
889  *
890  * tail is nParagraph - nParam1
891  *
892  * 3.
893  * ... nParam1 ... nParam2 ... nParagraph ...
894  * [xxxxxxxxxxx]___________|____________
895  * becomes
896  * ___________|____________[xxxxxxxxxxx]
897  *
898  * tail is nParam2 - nParam1
899  */
900 
901  // sort nParagraph, nParam1 and nParam2 in ascending order, calc range
902  if( nMiddle < nFirst )
903  {
904  ::std::swap(nFirst, nMiddle);
905  }
906  else if( nMiddle < nLast )
907  {
908  nLast = nLast + nMiddle - nFirst;
909  }
910  else
911  {
912  ::std::swap(nMiddle, nLast);
913  nLast = nLast + nMiddle - nFirst;
914  }
915 
916  if( !(nFirst < nParas && nMiddle < nParas && nLast < nParas) )
917  return;
918 
919  // since we have no "paragraph index
920  // changed" event on UAA, remove
921  // [first,last] and insert again later (in
922  // UpdateVisibleChildren)
923 
924  // maParaManager.Rotate( nFirst, nMiddle, nLast );
925 
926  // send CHILD_EVENT to affected children
927  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
928  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
929 
930  ::std::advance( begin, nFirst );
931  ::std::advance( end, nLast+1 );
932 
933  // TODO: maybe optimize here in the following way. If the
934  // number of removed children exceeds a certain threshold,
935  // use InvalidateFlags::Children
936  AccessibleTextHelper_LostChildEvent aFunctor( *this );
937 
938  ::std::for_each( begin, end, aFunctor );
939 
940  maParaManager.Release(nFirst, nLast+1);
941  // should be no need for UpdateBoundRect, since all affected children are cleared.
942  }
943 
944  namespace {
945 
946  // functor for sending child events (no stand-alone function, they are maybe not inlined)
947  class AccessibleTextHelper_ChildrenTextChanged
948  {
949  public:
950  void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
951  {
952  rPara.TextChanged();
953  }
954  };
955 
961  class AccessibleTextHelper_QueueFunctor
962  {
963  public:
964  AccessibleTextHelper_QueueFunctor() :
965  mnParasChanged( 0 ),
966  mnParaIndex(-1),
968  {}
969  void operator()( const SfxHint* pEvent )
970  {
971  if( !pEvent || mnParasChanged == -1 )
972  return;
973 
974  // determine hint type
975  const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent );
976  const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent );
977 
978  if( !(!pEditSourceHint && pTextHint &&
979  (pTextHint->GetId() == SfxHintId::TextParaInserted ||
980  pTextHint->GetId() == SfxHintId::TextParaRemoved )) )
981  return;
982 
983  if( pTextHint->GetValue() == EE_PARA_ALL )
984  {
985  mnParasChanged = -1;
986  }
987  else
988  {
989  mnHintId = pTextHint->GetId();
990  mnParaIndex = pTextHint->GetValue();
991  ++mnParasChanged;
992  }
993  }
994 
1000  sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; }
1006  sal_Int32 GetParaIndex() const { return mnParaIndex; }
1011  SfxHintId GetHintId() const { return mnHintId; }
1012 
1013  private:
1017  sal_Int32 mnParasChanged;
1019  sal_Int32 mnParaIndex;
1022  };
1023 
1024  }
1025 
1027  {
1028  // inspect queue for paragraph insert/remove events. If there
1029  // is exactly _one_ of those in the queue, and the number of
1030  // paragraphs has changed by exactly one, use that event to
1031  // determine a priori which paragraph was added/removed. This
1032  // is necessary, since I must sync right here with the
1033  // EditEngine state (number of paragraphs etc.), since I'm
1034  // potentially sending listener events right away.
1035  AccessibleTextHelper_QueueFunctor aFunctor;
1036  maEventQueue.ForEach( aFunctor );
1037 
1038  const sal_Int32 nNewParas( GetTextForwarder().GetParagraphCount() );
1039  const sal_Int32 nCurrParas( maParaManager.GetNum() );
1040 
1041  // whether every paragraph already is updated (no need to
1042  // repeat that later on, e.g. for PARA_MOVED events)
1043  bool bEverythingUpdated( false );
1044 
1045  if( std::abs( nNewParas - nCurrParas ) == 1 &&
1046  aFunctor.GetNumberOfParasChanged() == 1 )
1047  {
1048  // #103483# Exactly one paragraph added/removed. This is
1049  // the normal case, optimize event handling here.
1050 
1051  if( aFunctor.GetHintId() == SfxHintId::TextParaInserted )
1052  {
1053  // update num of paras
1054  maParaManager.SetNum( nNewParas );
1055 
1056  // release everything from the insertion position until the end
1057  maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1058 
1059  // TODO: Clarify whether this behaviour _really_ saves
1060  // anybody anything!
1061  // update children, _don't_ broadcast
1062  UpdateVisibleChildren( false );
1063  UpdateBoundRect();
1064 
1065  // send insert event
1066  // #109864# Enforce creation of this paragraph
1067  try
1068  {
1069  GotPropertyEvent( uno::makeAny( getAccessibleChild( aFunctor.GetParaIndex() -
1071  AccessibleEventId::CHILD );
1072  }
1073  catch( const uno::Exception& )
1074  {
1075  OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph");
1076  }
1077  }
1078  else if( aFunctor.GetHintId() == SfxHintId::TextParaRemoved )
1079  {
1080  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
1081  ::std::advance( begin, aFunctor.GetParaIndex() );
1082  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
1083  ::std::advance( end, 1 );
1084 
1085  // #i61812# remember para to be removed for later notification
1086  // AFTER the new state is applied (that after the para got removed)
1087  ::uno::Reference< XAccessible > xPara(begin->first.get().get());
1088 
1089  // release everything from the remove position until the end
1090  maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1091 
1092  // update num of paras
1093  maParaManager.SetNum( nNewParas );
1094 
1095  // TODO: Clarify whether this behaviour _really_ saves
1096  // anybody anything!
1097  // update children, _don't_ broadcast
1098  UpdateVisibleChildren( false );
1099  UpdateBoundRect();
1100 
1101  // #i61812# notification for removed para
1102  if (xPara.is())
1103  FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny( xPara) );
1104  }
1105 #ifdef DBG_UTIL
1106  else
1107  OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue() invalid hint id");
1108 #endif
1109  }
1110  else if( nNewParas != nCurrParas )
1111  {
1112  // release all paras
1113  maParaManager.Release(0, nCurrParas);
1114 
1115  // update num of paras
1116  maParaManager.SetNum( nNewParas );
1117 
1118  // #109864# create from scratch, don't broadcast
1119  UpdateVisibleChildren( false );
1120  UpdateBoundRect();
1121 
1122  // number of paragraphs somehow changed - but we have no
1123  // chance determining how. Thus, throw away everything and
1124  // create from scratch.
1125  // (child events should be broadcast after the changes are done...)
1126  FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
1127 
1128  // no need for further updates later on
1129  bEverythingUpdated = true;
1130  }
1131 
1132  while( !maEventQueue.IsEmpty() )
1133  {
1134  ::std::unique_ptr< SfxHint > pHint( maEventQueue.PopFront() );
1135  if (pHint)
1136  {
1137  const SfxHint& rHint = *pHint;
1138 
1139  // Note, if you add events here, you need to update the AccessibleTextEventQueue::Append
1140  // code, because only the events we process here, are actually queued there.
1141 
1142  try
1143  {
1144 
1145  if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1146  {
1147  const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1148 
1149  switch( pSdrHint->GetKind() )
1150  {
1152  {
1153  if(!IsActive())
1154  {
1155  break;
1156  }
1157  // change children state
1158  maParaManager.SetActive();
1159 
1160  // per definition, edit mode text has the focus
1161  SetFocus( true );
1162  break;
1163  }
1164 
1165  case SdrHintKind::EndEdit:
1166  {
1167  // focused child now loses focus
1168  ESelection aSelection;
1169  if( GetEditViewForwarder().GetSelection( aSelection ) )
1170  SetChildFocus( aSelection.nEndPara, false );
1171 
1172  // change children state
1173  maParaManager.SetActive( false );
1174 
1177  break;
1178  }
1179  default:
1180  break;
1181  }
1182  }
1183  else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1184  {
1185  switch( pEditSourceHint->GetId() )
1186  {
1187  case SfxHintId::EditSourceParasMoved:
1188  {
1189  DBG_ASSERT( pEditSourceHint->GetStartValue() < GetTextForwarder().GetParagraphCount() &&
1190  pEditSourceHint->GetEndValue() < GetTextForwarder().GetParagraphCount(),
1191  "AccessibleTextHelper_Impl::NotifyHdl: Invalid notification");
1192 
1193  if( !bEverythingUpdated )
1194  {
1195  ParagraphsMoved(pEditSourceHint->GetStartValue(),
1196  pEditSourceHint->GetValue(),
1197  pEditSourceHint->GetEndValue());
1198 
1199  // in all cases, check visibility afterwards.
1201  }
1202  break;
1203  }
1204 
1205  case SfxHintId::EditSourceSelectionChanged:
1206  // notify listeners
1207  try
1208  {
1209  UpdateSelection();
1210  }
1211  // maybe we're not in edit mode (this is not an error)
1212  catch( const uno::Exception& ) {}
1213  break;
1214  default: break;
1215  }
1216  }
1217  else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1218  {
1219  const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
1220 
1221  switch( pTextHint->GetId() )
1222  {
1223  case SfxHintId::TextModified:
1224  {
1225  // notify listeners
1226  sal_Int32 nPara( pTextHint->GetValue() );
1227 
1228  // #108900# Delegate change event to children
1229  AccessibleTextHelper_ChildrenTextChanged aNotifyChildrenFunctor;
1230 
1231  if( nPara == EE_PARA_ALL )
1232  {
1233  // #108900# Call every child
1234  ::std::for_each( maParaManager.begin(), maParaManager.end(),
1235  AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1236  }
1237  else
1238  if( nPara < nParas )
1239  {
1240  // #108900# Call child at index nPara
1241  ::std::for_each( maParaManager.begin()+nPara, maParaManager.begin()+nPara+1,
1242  AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1243  }
1244  break;
1245  }
1246 
1247  case SfxHintId::TextParaInserted:
1248  // already happened above
1249  break;
1250 
1251  case SfxHintId::TextParaRemoved:
1252  // already happened above
1253  break;
1254 
1255  case SfxHintId::TextHeightChanged:
1256  // visibility changed, done below
1257  break;
1258 
1259  case SfxHintId::TextViewScrolled:
1260  // visibility changed, done below
1261  break;
1262  default: break;
1263  }
1264 
1265  // in all cases, check visibility afterwards.
1267  UpdateBoundRect();
1268  }
1269  else if ( dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1270  {
1271  // just check visibility
1273  UpdateBoundRect();
1274  }
1275  // it's VITAL to keep the SfxSimpleHint last! It's the base of some classes above!
1276  else if( rHint.GetId() == SfxHintId::Dying)
1277  {
1278  // edit source is dying under us, become defunc then
1279  try
1280  {
1281  // make edit source inaccessible
1282  // Note: cannot destroy it here, since we're called from there!
1284  }
1285  catch( const uno::Exception& ) {}
1286  }
1287  }
1288  catch( const uno::Exception& )
1289  {
1290  DBG_UNHANDLED_EXCEPTION("svx");
1291  }
1292  }
1293  }
1294  }
1295 
1297  {
1298  // precondition: solar mutex locked
1300 
1301  // precondition: not in a recursion
1302  if( mbInNotify )
1303  return;
1304 
1305  mbInNotify = true;
1306 
1307  try
1308  {
1309  // Process notification event, arranged in order of likelihood of
1310  // occurrence to avoid unnecessary dynamic_cast. Note that
1311  // SvxEditSourceHint is derived from TextHint, so has to be checked
1312  // before that.
1313  if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1314  {
1315  const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1316  // process drawing layer events right away, if not
1317  // within an open EE notification frame. Otherwise,
1318  // event processing would be delayed until next EE
1319  // notification sequence.
1320  maEventQueue.Append( *pSdrHint );
1321  }
1322  else if( const SvxViewChangedHint* pViewHint = dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1323  {
1324  // process visibility right away, if not within an
1325  // open EE notification frame. Otherwise, event
1326  // processing would be delayed until next EE
1327  // notification sequence.
1328  maEventQueue.Append( *pViewHint );
1329  }
1330  else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1331  {
1332  // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1333  maEventQueue.Append( *pEditSourceHint );
1334  }
1335  else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1336  {
1337  // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1338  if(pTextHint->GetId() == SfxHintId::TextProcessNotifications)
1339  ProcessQueue();
1340  else
1341  maEventQueue.Append( *pTextHint );
1342  }
1343  // it's VITAL to keep the SfxHint last! It's the base of the classes above!
1344  else if( rHint.GetId() == SfxHintId::Dying )
1345  {
1346  // handle this event _at once_, because after that, objects are invalid
1347  // edit source is dying under us, become defunc then
1348  maEventQueue.Clear();
1349  try
1350  {
1351  // make edit source inaccessible
1352  // Note: cannot destroy it here, since we're called from there!
1354  }
1355  catch( const uno::Exception& ) {}
1356  }
1357  }
1358  catch( const uno::Exception& )
1359  {
1360  DBG_UNHANDLED_EXCEPTION("svx");
1361  mbInNotify = false;
1362  }
1363 
1364  mbInNotify = false;
1365  }
1366 
1368  {
1369  if( getNotifierClientId() != -1 )
1370  {
1371  try
1372  {
1373  // #106234# Unregister from EventNotifier
1375  SAL_INFO("svx", "disposed ID: " << mnNotifierClientId );
1376  }
1377  catch( const uno::Exception& ) {}
1378 
1379  mnNotifierClientId = -1;
1380  }
1381 
1382  try
1383  {
1384  // dispose children
1385  maParaManager.Dispose();
1386  }
1387  catch( const uno::Exception& ) {}
1388 
1389  // quit listen on stale edit source
1390  if( maEditSource.IsValid() )
1391  EndListening( maEditSource.GetBroadcaster() );
1392 
1393  // clear references
1394  maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
1395  mxFrontEnd = nullptr;
1396  }
1397 
1398  void AccessibleTextHelper_Impl::FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const
1399  {
1400  // -- object locked --
1401  AccessibleEventObject aEvent;
1402  {
1403  osl::MutexGuard aGuard(maMutex);
1404 
1405  DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper::FireEvent: no event source set");
1406 
1407  if (mxFrontEnd.is())
1408  aEvent = AccessibleEventObject(mxFrontEnd->getAccessibleContext(), nEventId,
1409  rNewValue, rOldValue);
1410  else
1411  aEvent = AccessibleEventObject(uno::Reference<uno::XInterface>(), nEventId,
1412  rNewValue, rOldValue);
1413 
1414  // no locking necessary, FireEvent internally copies listeners
1415  // if someone removes/adds in between Further locking,
1416  // actually, might lead to deadlocks, since we're calling out
1417  // of this object
1418  }
1419  // -- until here --
1420 
1421  FireEvent(aEvent);
1422  }
1423 
1424  void AccessibleTextHelper_Impl::FireEvent( const AccessibleEventObject& rEvent ) const
1425  {
1426  // #102261# Call global queue for focus events
1427  if( rEvent.EventId == AccessibleStateType::FOCUSED )
1429 
1430  // #106234# Delegate to EventNotifier
1432  rEvent );
1433  }
1434 
1435  // XAccessibleContext
1437  {
1439  }
1440 
1441  uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int32 i )
1442  {
1443  i -= GetStartIndex();
1444 
1445  if( 0 > i || i >= getAccessibleChildCount() ||
1446  GetTextForwarder().GetParagraphCount() <= i )
1447  {
1448  throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd);
1449  }
1450 
1451  DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set");
1452 
1453  if( mxFrontEnd.is() )
1454  return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first;
1455  else
1456  return nullptr;
1457  }
1458 
1459  void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1460  {
1461  if( getNotifierClientId() != -1 )
1463  }
1464 
1465  void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1466  {
1467  if( getNotifierClientId() == -1 )
1468  return;
1469 
1470  const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener );
1471  if ( !nListenerCount )
1472  {
1473  // no listeners anymore
1474  // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client),
1475  // and at least to us not firing any events anymore, in case somebody calls
1476  // NotifyAccessibleEvent, again
1478  mnNotifierClientId = -1;
1480  }
1481  }
1482 
1483  uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint )
1484  {
1485  // make given position relative
1486  if( !mxFrontEnd.is() )
1487  throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1488 
1489  uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext();
1490 
1491  if( !xFrontEndContext.is() )
1492  throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1493 
1494  uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW );
1495 
1496  // #103862# No longer need to make given position relative
1497  Point aPoint( _aPoint.X, _aPoint.Y );
1498 
1499  // respect EditEngine offset to surrounding shape/cell
1500  aPoint -= GetOffset();
1501 
1502  // convert to EditEngine coordinate system
1503  SvxTextForwarder& rCacheTF = GetTextForwarder();
1504  Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) );
1505 
1506  // iterate over all visible children (including those not yet created)
1507  sal_Int32 nChild;
1508  for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild )
1509  {
1510  DBG_ASSERT(nChild >= 0,
1511  "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow");
1512 
1513  tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) );
1514 
1515  if( aParaBounds.IsInside( aLogPoint ) )
1516  return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() );
1517  }
1518 
1519  // found none
1520  return nullptr;
1521  }
1522 
1523 
1524  // AccessibleTextHelper implementation (simply forwards to impl)
1525 
1526  AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) :
1527  mpImpl( new AccessibleTextHelper_Impl() )
1528  {
1529  SolarMutexGuard aGuard;
1530 
1531  SetEditSource( std::move(pEditSource) );
1532  }
1533 
1535  {
1536  }
1537 
1539  {
1540 #ifdef DBG_UTIL
1541  mpImpl->CheckInvariants();
1542 
1543  const SvxEditSource& aEditSource = mpImpl->GetEditSource();
1544 
1545  mpImpl->CheckInvariants();
1546 
1547  return aEditSource;
1548 #else
1549  return mpImpl->GetEditSource();
1550 #endif
1551  }
1552 
1553  void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
1554  {
1555 #ifdef DBG_UTIL
1556  // precondition: solar mutex locked
1558 
1559  mpImpl->CheckInvariants();
1560 #endif
1561 
1562  mpImpl->SetEditSource( std::move(pEditSource) );
1563 
1564 #ifdef DBG_UTIL
1565  mpImpl->CheckInvariants();
1566 #endif
1567  }
1568 
1569  void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface )
1570  {
1571 #ifdef DBG_UTIL
1572  mpImpl->CheckInvariants();
1573 #endif
1574 
1575  mpImpl->SetEventSource( rInterface );
1576 
1577 #ifdef DBG_UTIL
1578  mpImpl->CheckInvariants();
1579 #endif
1580  }
1581 
1582  void AccessibleTextHelper::SetFocus( bool bHaveFocus )
1583  {
1584 #ifdef DBG_UTIL
1585  // precondition: solar mutex locked
1587 
1588  mpImpl->CheckInvariants();
1589 #endif
1590 
1591  mpImpl->SetFocus( bHaveFocus );
1592 
1593 #ifdef DBG_UTIL
1594  mpImpl->CheckInvariants();
1595 #endif
1596  }
1597 
1599  {
1600 #ifdef DBG_UTIL
1601  mpImpl->CheckInvariants();
1602 
1603  bool bRet( mpImpl->HaveFocus() );
1604 
1605  mpImpl->CheckInvariants();
1606 
1607  return bRet;
1608 #else
1609  return mpImpl->HaveFocus();
1610 #endif
1611  }
1612 
1613  void AccessibleTextHelper::SetOffset( const Point& rPoint )
1614  {
1615 #ifdef DBG_UTIL
1616  // precondition: solar mutex locked
1618 
1619  mpImpl->CheckInvariants();
1620 #endif
1621 
1622  mpImpl->SetOffset( rPoint );
1623 
1624 #ifdef DBG_UTIL
1625  mpImpl->CheckInvariants();
1626 #endif
1627  }
1628 
1629  void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset )
1630  {
1631 #ifdef DBG_UTIL
1632  // precondition: solar mutex locked
1634 
1635  mpImpl->CheckInvariants();
1636 #endif
1637 
1638  mpImpl->SetStartIndex( nOffset );
1639 
1640 #ifdef DBG_UTIL
1641  mpImpl->CheckInvariants();
1642 #endif
1643  }
1644 
1646  {
1647 #ifdef DBG_UTIL
1648  mpImpl->CheckInvariants();
1649 
1650  sal_Int32 nOffset = mpImpl->GetStartIndex();
1651 
1652  mpImpl->CheckInvariants();
1653 
1654  return nOffset;
1655 #else
1656  return mpImpl->GetStartIndex();
1657 #endif
1658  }
1659 
1660  void AccessibleTextHelper::SetAdditionalChildStates( const VectorOfStates& rChildStates )
1661  {
1662  mpImpl->SetAdditionalChildStates( rChildStates );
1663  }
1664 
1666  {
1667 #ifdef DBG_UTIL
1668  // precondition: solar mutex locked
1670 
1671  mpImpl->CheckInvariants();
1672 #endif
1673 
1674  mpImpl->UpdateVisibleChildren();
1675  mpImpl->UpdateBoundRect();
1676 
1677  mpImpl->UpdateSelection();
1678 
1679 #ifdef DBG_UTIL
1680  mpImpl->CheckInvariants();
1681 #endif
1682  }
1683 
1685  {
1686  // As Dispose calls ShutdownEditSource, which in turn
1687  // deregisters as listener on the edit source, have to lock
1688  // here
1689  SolarMutexGuard aGuard;
1690 
1691 #ifdef DBG_UTIL
1692  mpImpl->CheckInvariants();
1693 #endif
1694 
1695  mpImpl->Dispose();
1696 
1697 #ifdef DBG_UTIL
1698  mpImpl->CheckInvariants();
1699 #endif
1700  }
1701 
1702  // XAccessibleContext
1704  {
1705  SolarMutexGuard aGuard;
1706 
1707 #ifdef DBG_UTIL
1708  mpImpl->CheckInvariants();
1709 
1710  sal_Int32 nRet = mpImpl->getAccessibleChildCount();
1711 
1712  mpImpl->CheckInvariants();
1713 
1714  return nRet;
1715 #else
1716  return mpImpl->getAccessibleChildCount();
1717 #endif
1718  }
1719 
1720  uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int32 i )
1721  {
1722  SolarMutexGuard aGuard;
1723 
1724 #ifdef DBG_UTIL
1725  mpImpl->CheckInvariants();
1726 
1727  uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i );
1728 
1729  mpImpl->CheckInvariants();
1730 
1731  return xRet;
1732 #else
1733  return mpImpl->getAccessibleChild( i );
1734 #endif
1735  }
1736 
1737  void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1738  {
1739 #ifdef DBG_UTIL
1740  mpImpl->CheckInvariants();
1741 
1742  mpImpl->addAccessibleEventListener( xListener );
1743 
1744  mpImpl->CheckInvariants();
1745 #else
1746  mpImpl->addAccessibleEventListener( xListener );
1747 #endif
1748  }
1749 
1750  void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1751  {
1752 #ifdef DBG_UTIL
1753  mpImpl->CheckInvariants();
1754 
1755  mpImpl->removeAccessibleEventListener( xListener );
1756 
1757  mpImpl->CheckInvariants();
1758 #else
1759  mpImpl->removeAccessibleEventListener( xListener );
1760 #endif
1761  }
1762 
1763  // XAccessibleComponent
1764  uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint )
1765  {
1766  SolarMutexGuard aGuard;
1767 
1768 #ifdef DBG_UTIL
1769  mpImpl->CheckInvariants();
1770 
1771  uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint );
1772 
1773  mpImpl->CheckInvariants();
1774 
1775  return xChild;
1776 #else
1777  return mpImpl->getAccessibleAtPoint( aPoint );
1778 #endif
1779  }
1780 
1781 } // end of namespace accessibility
1782 
1783 
1784 /* 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:124
void GetSelection(struct ESelection &rSel, SvxTextForwarder const *pForwarder)
void UpdateVisibleChildren(bool bBroadcastEvents=true)
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)
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_uLong GetValue() const
sal_Int32 GetChildCount() const
Implements getAccessibleChildCount.
sal_Int32 mnStartIndex
void SetAdditionalChildStates(const VectorOfStates &rChildStates)
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 SetAdditionalChildStates(const VectorOfStates &rChildStates)
Sets a vector of additional accessible states.
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.
#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