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 
21 // Global header
22 
23 
24 #include <memory>
25 #include <utility>
26 #include <algorithm>
27 #include <osl/mutex.hxx>
28 #include <sal/log.hxx>
29 #include <com/sun/star/uno/Any.hxx>
30 #include <com/sun/star/uno/Reference.hxx>
31 #include <com/sun/star/awt/Point.hpp>
32 #include <com/sun/star/awt/Rectangle.hpp>
33 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
34 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
35 #include <com/sun/star/accessibility/XAccessible.hpp>
36 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
37 #include <com/sun/star/accessibility/XAccessibleComponent.hpp>
38 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
40 #include <vcl/svapp.hxx>
41 #include <vcl/textdata.hxx>
42 #include <vcl/unohelp.hxx>
43 
44 
45 // Project-local header
46 
47 
50 
51 #include <editeng/unoedhlp.hxx>
52 #include <editeng/unoedprx.hxx>
55 #include <svx/svdmodel.hxx>
56 #include <svx/svdpntv.hxx>
57 #include <cell.hxx>
58 #include "../table/accessiblecell.hxx"
59 #include <editeng/editdata.hxx>
60 #include <tools/debug.hxx>
61 #include <tools/diagnose_ex.h>
62 
63 using namespace ::com::sun::star;
64 using namespace ::com::sun::star::accessibility;
65 
66 namespace accessibility
67 {
68 
69 // AccessibleTextHelper_Impl declaration
70 
71  template < typename first_type, typename second_type >
72  static ::std::pair< first_type, second_type > makeSortedPair( first_type first,
73  second_type second )
74  {
75  if( first > second )
76  return ::std::make_pair( second, first );
77  else
78  return ::std::make_pair( first, second );
79  }
80 
82  {
83  public:
84  typedef ::std::vector< sal_Int16 > VectorOfStates;
85 
86  // receive pointer to our frontend class and view window
88  virtual ~AccessibleTextHelper_Impl() override;
89 
90  // XAccessibleContext child handling methods
91  sal_Int32 getAccessibleChildCount() const;
92  uno::Reference< XAccessible > getAccessibleChild( sal_Int32 i );
93 
94  // XAccessibleEventBroadcaster child related methods
95  void addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener );
96  void removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener );
97 
98  // XAccessibleComponent child related methods
99  uno::Reference< XAccessible > getAccessibleAtPoint( const awt::Point& aPoint );
100 
101  SvxEditSourceAdapter& GetEditSource() const;
102 
103  void SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource );
104 
105  void SetEventSource( const uno::Reference< XAccessible >& rInterface )
106  {
107  mxFrontEnd = rInterface;
108  }
109 
110  void SetOffset( const Point& );
111  Point GetOffset() const
112  {
113  ::osl::MutexGuard aGuard( maMutex ); Point aPoint( maOffset );
114  return aPoint;
115  }
116 
117  void SetStartIndex( sal_Int32 nOffset );
118  sal_Int32 GetStartIndex() const
119  {
120  // Strictly correct only with locked solar mutex, // but
121  // here we rely on the fact that sal_Int32 access is
122  // atomic
123  return mnStartIndex;
124  }
125 
126  void SetAdditionalChildStates( const VectorOfStates& rChildStates );
127 
128  void Dispose();
129 
130  // do NOT hold object mutex when calling this! Danger of deadlock
131  void FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue = uno::Any(), const uno::Any& rOldValue = uno::Any() ) const;
132  void FireEvent( const AccessibleEventObject& rEvent ) const;
133 
134  void SetFocus( bool bHaveFocus );
135  bool HaveFocus()
136  {
137  // No locking of solar mutex here, since we rely on the fact
138  // that sal_Bool access is atomic
139  return mbThisHasFocus;
140  }
141  void SetChildFocus( sal_Int32 nChild, bool bHaveFocus );
142  void SetShapeFocus( bool bHaveFocus );
143  void ChangeChildFocus( sal_Int32 nNewChild );
144 
145 #ifdef DBG_UTIL
146  void CheckInvariants() const;
147 #endif
148 
149  // checks all children for visibility, throws away invisible ones
150  void UpdateVisibleChildren( bool bBroadcastEvents=true );
151 
152  // check all children for changes in position and size
153  void UpdateBoundRect();
154 
155  // calls SetSelection on the forwarder and updates maLastSelection
156  // cache.
157  void UpdateSelection();
158 
159  private:
160 
161  // Process event queue
162  void ProcessQueue();
163 
164  // syntactic sugar for FireEvent
165  void GotPropertyEvent( const uno::Any& rNewValue, const sal_Int16 nEventId ) const { FireEvent( nEventId, rNewValue ); }
166 
167  // shutdown usage of current edit source on myself and the children.
168  void ShutdownEditSource();
169 
170  void ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast );
171 
172  virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
173 
174  int getNotifierClientId() const { return mnNotifierClientId; }
175 
176  // lock solar mutex before
178  // lock solar mutex before
180  // lock solar mutex before
182 
183  // are we in edit mode?
184  bool IsActive() const;
185 
186  // our frontend class (the one implementing the actual
187  // interface). That's not necessarily the one containing the impl
188  // pointer!
189  uno::Reference< XAccessible > mxFrontEnd;
190 
191  // a wrapper for the text forwarders (guarded by solar mutex)
192  mutable SvxEditSourceAdapter maEditSource;
193 
194  // store last selection (to correctly report selection changes, guarded by solar mutex)
196 
197  // cache range of visible children (guarded by solar mutex)
200 
201  // offset to add to all our children (unguarded, relying on
202  // the fact that sal_Int32 access is atomic)
203  sal_Int32 mnStartIndex;
204 
205  // the object handling our children (guarded by solar mutex)
206  ::accessibility::AccessibleParaManager maParaManager;
207 
208  // Queued events from Notify() (guarded by solar mutex)
210 
211  // spin lock to prevent notify in notify (guarded by solar mutex)
213 
214  // whether the object or its children has the focus set (guarded by solar mutex)
216 
217  // whether we (this object) has the focus set (guarded by solar mutex)
219 
220  mutable ::osl::Mutex maMutex;
221 
224 
227  };
228 
231  mnFirstVisibleChild( -1 ),
232  mnLastVisibleChild( -2 ),
233  mnStartIndex( 0 ),
234  mbInNotify( false ),
235  mbGroupHasFocus( false ),
236  mbThisHasFocus( false ),
237  maOffset(0,0),
238  // well, that's strictly exception safe, though not really
239  // robust. We rely on the fact that this member is constructed
240  // last, and that the constructor body is empty, thus no
241  // chance for exceptions once the Id is fetched. Nevertheless,
242  // normally should employ RAII here...
243  mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient())
244  {
245  SAL_INFO("svx", "received ID: " << mnNotifierClientId );
246  }
247 
249  {
250  SolarMutexGuard aGuard;
251 
252  try
253  {
254  // call Dispose here, too, since we've some resources not
255  // automatically freed otherwise
256  Dispose();
257  }
258  catch( const uno::Exception& ) {}
259  }
260 
262  {
263  if( !maEditSource.IsValid() )
264  throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
265 
266  SvxTextForwarder* pTextForwarder = maEditSource.GetTextForwarder();
267 
268  if( !pTextForwarder )
269  throw uno::RuntimeException("Unable to fetch text forwarder, model might be dead", mxFrontEnd);
270 
271  if( !pTextForwarder->IsValid() )
272  throw uno::RuntimeException("Text forwarder is invalid, model might be dead", mxFrontEnd);
273 
274  return *pTextForwarder;
275  }
276 
278  {
279  if( !maEditSource.IsValid() )
280  throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
281 
282  SvxViewForwarder* pViewForwarder = maEditSource.GetViewForwarder();
283 
284  if( !pViewForwarder )
285  throw uno::RuntimeException("Unable to fetch view forwarder, model might be dead", mxFrontEnd);
286 
287  if( !pViewForwarder->IsValid() )
288  throw uno::RuntimeException("View forwarder is invalid, model might be dead", mxFrontEnd);
289 
290  return *pViewForwarder;
291  }
292 
294  {
295  if( !maEditSource.IsValid() )
296  throw uno::RuntimeException("Unknown edit source", mxFrontEnd);
297 
298  SvxEditViewForwarder* pViewForwarder = maEditSource.GetEditViewForwarder();
299 
300  if( !pViewForwarder )
301  {
302  throw uno::RuntimeException("No edit view forwarder, object not in edit mode", mxFrontEnd);
303  }
304 
305  if( !pViewForwarder->IsValid() )
306  {
307  throw uno::RuntimeException("View forwarder is invalid, object not in edit mode", mxFrontEnd);
308  }
309 
310  return *pViewForwarder;
311  }
312 
313  SvxEditSourceAdapter& AccessibleTextHelper_Impl::GetEditSource() const
314  {
315  if( !maEditSource.IsValid() )
316  throw uno::RuntimeException("AccessibleTextHelper_Impl::GetEditSource: no edit source", mxFrontEnd );
317  return maEditSource;
318  }
319 
320  namespace {
321 
322  // functor for sending child events (no stand-alone function, they are maybe not inlined)
323  class AccessibleTextHelper_OffsetChildIndex
324  {
325  public:
326  explicit AccessibleTextHelper_OffsetChildIndex( sal_Int32 nDifference ) : mnDifference(nDifference) {}
327  void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
328  {
329  rPara.SetIndexInParent( rPara.GetIndexInParent() + mnDifference );
330  }
331 
332  private:
333  const sal_Int32 mnDifference;
334  };
335 
336  }
337 
339  {
340  sal_Int32 nOldOffset( mnStartIndex );
341 
342  mnStartIndex = nOffset;
343 
344  if( nOldOffset != nOffset )
345  {
346  // update children
347  AccessibleTextHelper_OffsetChildIndex aFunctor( nOffset - nOldOffset );
348 
349  ::std::for_each( maParaManager.begin(), maParaManager.end(),
350  AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_OffsetChildIndex > (aFunctor) );
351  }
352  }
353 
354  void AccessibleTextHelper_Impl::SetAdditionalChildStates( const VectorOfStates& rChildStates )
355  {
356  maParaManager.SetAdditionalChildStates( rChildStates );
357  }
358 
359  void AccessibleTextHelper_Impl::SetChildFocus( sal_Int32 nChild, bool bHaveFocus )
360  {
361  if( bHaveFocus )
362  {
363  if( mbThisHasFocus )
364  SetShapeFocus( false );
365 
366  maParaManager.SetFocus( nChild );
367 
368  // we just received the focus, also send caret event then
369  UpdateSelection();
370 
371  SAL_INFO("svx", "Paragraph " << nChild << " received focus");
372  }
373  else
374  {
375  maParaManager.SetFocus( -1 );
376 
377  SAL_INFO("svx", "Paragraph " << nChild << " lost focus");
378 
379  if( mbGroupHasFocus )
380  SetShapeFocus( true );
381  }
382  }
383 
385  {
386  if( mbThisHasFocus )
387  SetShapeFocus( false );
388 
389  mbGroupHasFocus = true;
390  maParaManager.SetFocus( nNewChild );
391 
392  SAL_INFO("svx", "Paragraph " << nNewChild << " received focus");
393  }
394 
396  {
397  bool bOldFocus( mbThisHasFocus );
398 
399  mbThisHasFocus = bHaveFocus;
400 
401  if( bOldFocus != bHaveFocus )
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 
440  void AccessibleTextHelper_Impl::SetFocus( bool bHaveFocus )
441  {
442  bool bOldFocus( mbGroupHasFocus );
443 
444  mbGroupHasFocus = bHaveFocus;
445 
446  if( IsActive() )
447  {
448  try
449  {
450  // find the one with the cursor and get/set focus accordingly
451  ESelection aSelection;
452  if( GetEditViewForwarder().GetSelection( aSelection ) )
453  SetChildFocus( aSelection.nEndPara, bHaveFocus );
454  }
455  catch( const uno::Exception& ) {}
456  }
457  else if( bOldFocus != bHaveFocus )
458  {
459  SetShapeFocus( bHaveFocus );
460  }
461 
462  SAL_INFO("svx", "focus changed, Object " << this << ", state: " << (bHaveFocus ? "focused" : "not focused") );
463  }
464 
466  {
467  try
468  {
469  SvxEditSource& rEditSource = GetEditSource();
470  SvxEditViewForwarder* pViewForwarder = rEditSource.GetEditViewForwarder();
471 
472  if( !pViewForwarder )
473  return false;
474 
475  if( mxFrontEnd.is() )
476  {
477  AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() );
478  if ( pAccessibleCell )
479  {
480  sdr::table::CellRef xCell = pAccessibleCell->getCellRef();
481  if ( xCell.is() )
482  return xCell->IsActiveCell();
483  }
484  }
485  return pViewForwarder->IsValid();
486  }
487  catch( const uno::RuntimeException& )
488  {
489  return false;
490  }
491  }
492 
494  {
495  try
496  {
497  ESelection aSelection;
498  if( GetEditViewForwarder().GetSelection( aSelection ) )
499  {
500  if( maLastSelection != aSelection &&
501  aSelection.nEndPara < maParaManager.GetNum() )
502  {
503  // #103998# Not that important, changed from assertion to trace
504  if( mbThisHasFocus )
505  {
506  SAL_INFO("svx", "Parent has focus!");
507  }
508 
509  sal_Int32 nMaxValidParaIndex( GetTextForwarder().GetParagraphCount() - 1 );
510 
511  // notify all affected paragraphs (TODO: may be suboptimal,
512  // since some paragraphs might stay selected)
514  {
515  // Did the caret move from one paragraph to another?
516  // #100530# no caret events if not focused.
517  if( mbGroupHasFocus &&
518  maLastSelection.nEndPara != aSelection.nEndPara )
519  {
520  if( maLastSelection.nEndPara < maParaManager.GetNum() )
521  {
522  maParaManager.FireEvent( ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ),
523  ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex )+1,
524  AccessibleEventId::CARET_CHANGED,
525  uno::makeAny(static_cast<sal_Int32>(-1)),
526  uno::makeAny(maLastSelection.nEndPos) );
527  }
528 
529  ChangeChildFocus( aSelection.nEndPara );
530 
531  SAL_INFO(
532  "svx",
533  "focus changed, Object: " << this
534  << ", Paragraph: " << aSelection.nEndPara
535  << ", Last paragraph: "
537  }
538  }
539 
540  // #100530# no caret events if not focused.
541  if( mbGroupHasFocus )
542  {
543  uno::Any aOldCursor;
544 
545  // #i13705# The old cursor can only contain valid
546  // values if it's the same paragraph!
548  maLastSelection.nEndPara == aSelection.nEndPara )
549  {
550  aOldCursor <<= maLastSelection.nEndPos;
551  }
552  else
553  {
554  aOldCursor <<= static_cast<sal_Int32>(-1);
555  }
556 
557  maParaManager.FireEvent( aSelection.nEndPara,
558  aSelection.nEndPara+1,
559  AccessibleEventId::CARET_CHANGED,
560  uno::makeAny(aSelection.nEndPos),
561  aOldCursor );
562  }
563 
564  SAL_INFO(
565  "svx",
566  "caret changed, Object: " << this << ", New pos: "
567  << aSelection.nEndPos << ", Old pos: "
568  << maLastSelection.nEndPos << ", New para: "
569  << aSelection.nEndPara << ", Old para: "
571 
572  // #108947# Sort new range before calling FireEvent
573  ::std::pair<sal_Int32, sal_Int32> sortedSelection(
574  makeSortedPair(::std::min( aSelection.nStartPara, nMaxValidParaIndex ),
575  ::std::min( aSelection.nEndPara, nMaxValidParaIndex ) ) );
576 
577  // #108947# Sort last range before calling FireEvent
578  ::std::pair<sal_Int32, sal_Int32> sortedLastSelection(
579  makeSortedPair(::std::min( maLastSelection.nStartPara, nMaxValidParaIndex ),
580  ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ) ) );
581 
582  // event TEXT_SELECTION_CHANGED has to be submitted. (#i27299#)
583  const sal_Int16 nTextSelChgEventId =
584  AccessibleEventId::TEXT_SELECTION_CHANGED;
585  // #107037# notify selection change
587  {
588  // last selection is undefined
589  // use method <ESelection::HasRange()> (#i27299#)
590  if ( aSelection.HasRange() )
591  {
592  // selection was undefined, now is on
593  maParaManager.FireEvent( sortedSelection.first,
594  sortedSelection.second+1,
595  nTextSelChgEventId );
596  }
597  }
598  else
599  {
600  // last selection is valid
601  // use method <ESelection::HasRange()> (#i27299#)
602  if ( maLastSelection.HasRange() &&
603  !aSelection.HasRange() )
604  {
605  // selection was on, now is empty
606  maParaManager.FireEvent( sortedLastSelection.first,
607  sortedLastSelection.second+1,
608  nTextSelChgEventId );
609  }
610  // use method <ESelection::HasRange()> (#i27299#)
611  else if( !maLastSelection.HasRange() &&
612  aSelection.HasRange() )
613  {
614  // selection was empty, now is on
615  maParaManager.FireEvent( sortedSelection.first,
616  sortedSelection.second+1,
617  nTextSelChgEventId );
618  }
619  // no event TEXT_SELECTION_CHANGED event, if new and
620  // last selection are empty. (#i27299#)
621  else if ( maLastSelection.HasRange() &&
622  aSelection.HasRange() )
623  {
624  // use sorted last and new selection
625  ESelection aTmpLastSel( maLastSelection );
626  aTmpLastSel.Adjust();
627  ESelection aTmpSel( aSelection );
628  aTmpSel.Adjust();
629  // first submit event for new and changed selection
630  sal_Int32 nPara = aTmpSel.nStartPara;
631  for ( ; nPara <= aTmpSel.nEndPara; ++nPara )
632  {
633  if ( nPara < aTmpLastSel.nStartPara ||
634  nPara > aTmpLastSel.nEndPara )
635  {
636  // new selection on paragraph <nPara>
637  maParaManager.FireEvent( nPara,
638  nTextSelChgEventId );
639  }
640  else
641  {
642  // check for changed selection on paragraph <nPara>
643  const sal_Int32 nParaStartPos =
644  nPara == aTmpSel.nStartPara
645  ? aTmpSel.nStartPos : 0;
646  const sal_Int32 nParaEndPos =
647  nPara == aTmpSel.nEndPara
648  ? aTmpSel.nEndPos : -1;
649  const sal_Int32 nLastParaStartPos =
650  nPara == aTmpLastSel.nStartPara
651  ? aTmpLastSel.nStartPos : 0;
652  const sal_Int32 nLastParaEndPos =
653  nPara == aTmpLastSel.nEndPara
654  ? aTmpLastSel.nEndPos : -1;
655  if ( nParaStartPos != nLastParaStartPos ||
656  nParaEndPos != nLastParaEndPos )
657  {
658  maParaManager.FireEvent(
659  nPara, nTextSelChgEventId );
660  }
661  }
662  }
663  // second submit event for 'old' selections
664  nPara = aTmpLastSel.nStartPara;
665  for ( ; nPara <= aTmpLastSel.nEndPara; ++nPara )
666  {
667  if ( nPara < aTmpSel.nStartPara ||
668  nPara > aTmpSel.nEndPara )
669  {
670  maParaManager.FireEvent( nPara,
671  nTextSelChgEventId );
672  }
673  }
674  }
675  }
676 
677  maLastSelection = aSelection;
678  }
679  }
680  }
681  // no selection? no update actions
682  catch( const uno::RuntimeException& ) {}
683  }
684 
686  {
687  // This should only be called with solar mutex locked, i.e. from the main office thread
688 
689  // This here is somewhat clumsy: As soon as our children have
690  // a NULL EditSource (maParaManager.SetEditSource()), they
691  // enter the disposed state and cannot be reanimated. Thus, it
692  // is unavoidable and a hard requirement to let go and create
693  // from scratch each and every child.
694 
695  // invalidate children
696  maParaManager.Dispose();
697  maParaManager.SetNum(0);
698 
699  // lost all children
700  if( mxFrontEnd.is() )
701  FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
702 
703  // quit listen on stale edit source
704  if( maEditSource.IsValid() )
705  EndListening( maEditSource.GetBroadcaster() );
706 
707  maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
708  }
709 
710  void AccessibleTextHelper_Impl::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
711  {
712  // This should only be called with solar mutex locked, i.e. from the main office thread
713 
714  // shutdown old edit source
716 
717  // set new edit source
718  maEditSource.SetEditSource( std::move(pEditSource) );
719 
720  // init child vector to the current child count
721  if( maEditSource.IsValid() )
722  {
723  maParaManager.SetNum( GetTextForwarder().GetParagraphCount() );
724 
725  // listen on new edit source
726  StartListening( maEditSource.GetBroadcaster() );
727 
729  }
730  }
731 
732  void AccessibleTextHelper_Impl::SetOffset( const Point& rPoint )
733  {
734  // guard against non-atomic access to maOffset data structure
735  {
736  ::osl::MutexGuard aGuard( maMutex );
737  maOffset = rPoint;
738  }
739 
740  maParaManager.SetEEOffset( rPoint );
741 
742  // in all cases, check visibility afterwards.
744  UpdateBoundRect();
745  }
746 
748  {
749  try
750  {
751  SvxTextForwarder& rCacheTF = GetTextForwarder();
752  sal_Int32 nParas=rCacheTF.GetParagraphCount();
753 
754  mnFirstVisibleChild = -1;
755  mnLastVisibleChild = -2;
756 
757  for( sal_Int32 nCurrPara=0; nCurrPara<nParas; ++nCurrPara )
758  {
759  if (nCurrPara == 0)
760  mnFirstVisibleChild = nCurrPara;
761  mnLastVisibleChild = nCurrPara;
762  if (mxFrontEnd.is() && bBroadcastEvents)
763  {
764  // child not yet created?
765  ::accessibility::AccessibleParaManager::WeakChild aChild( maParaManager.GetChild(nCurrPara) );
766  if( aChild.second.Width == 0 &&
767  aChild.second.Height == 0 )
768  {
769  GotPropertyEvent( uno::makeAny( maParaManager.CreateChild( nCurrPara - mnFirstVisibleChild,
770  mxFrontEnd, GetEditSource(), nCurrPara ).first ),
771  AccessibleEventId::CHILD );
772  }
773  }
774  }
775  }
776  catch( const uno::Exception& )
777  {
778  OSL_FAIL("AccessibleTextHelper_Impl::UpdateVisibleChildren error while determining visible children");
779 
780  // something failed - currently no children
781  mnFirstVisibleChild = -1;
782  mnLastVisibleChild = -2;
783  maParaManager.SetNum(0);
784 
785  // lost all children
786  if( bBroadcastEvents )
787  FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
788  }
789  }
790 
791  namespace {
792 
793  // functor for checking changes in paragraph bounding boxes (no stand-alone function, maybe not inlined)
794  class AccessibleTextHelper_UpdateChildBounds
795  {
796  public:
797  explicit AccessibleTextHelper_UpdateChildBounds() {}
798  ::accessibility::AccessibleParaManager::WeakChild operator()( const ::accessibility::AccessibleParaManager::WeakChild& rChild )
799  {
800  // retrieve hard reference from weak one
801  auto aHardRef( rChild.first.get() );
802 
803  if( aHardRef.is() )
804  {
805  awt::Rectangle aNewRect = aHardRef->getBounds();
806  const awt::Rectangle& aOldRect = rChild.second;
807 
808  if( aNewRect.X != aOldRect.X ||
809  aNewRect.Y != aOldRect.Y ||
810  aNewRect.Width != aOldRect.Width ||
811  aNewRect.Height != aOldRect.Height )
812  {
813  // visible data changed
814  aHardRef->FireEvent( AccessibleEventId::BOUNDRECT_CHANGED );
815 
816  // update internal bounds
817  return ::accessibility::AccessibleParaManager::WeakChild( rChild.first, aNewRect );
818  }
819  }
820 
821  // identity transform
822  return rChild;
823  }
824  };
825 
826  }
827 
829  {
830  // send BOUNDRECT_CHANGED to affected children
831  AccessibleTextHelper_UpdateChildBounds aFunctor;
832  ::std::transform( maParaManager.begin(), maParaManager.end(), maParaManager.begin(), aFunctor );
833  }
834 
835 #ifdef DBG_UTIL
837  {
838  if( mnFirstVisibleChild >= 0 &&
840  {
841  OSL_FAIL( "AccessibleTextHelper: range invalid" );
842  }
843  }
844 #endif
845 
846  namespace {
847 
848  // functor for sending child events (no stand-alone function, they are maybe not inlined)
849  class AccessibleTextHelper_LostChildEvent
850  {
851  public:
852  explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {}
853  void operator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara )
854  {
855  // retrieve hard reference from weak one
856  auto aHardRef( rPara.first.get() );
857 
858  if( aHardRef.is() )
859  mrImpl.FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny<css::uno::Reference<css::accessibility::XAccessible>>(aHardRef.get()) );
860  }
861 
862  private:
863  AccessibleTextHelper_Impl& mrImpl;
864  };
865 
866  }
867 
868  void AccessibleTextHelper_Impl::ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast )
869  {
870  const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
871 
872  /* rotate paragraphs
873  * =================
874  *
875  * Three cases:
876  *
877  * 1.
878  * ... nParagraph ... nParam1 ... nParam2 ...
879  * |______________[xxxxxxxxxxx]
880  * becomes
881  * [xxxxxxxxxxx]|______________
882  *
883  * tail is 0
884  *
885  * 2.
886  * ... nParam1 ... nParagraph ... nParam2 ...
887  * [xxxxxxxxxxx|xxxxxxxxxxxxxx]____________
888  * becomes
889  * ____________[xxxxxxxxxxx|xxxxxxxxxxxxxx]
890  *
891  * tail is nParagraph - nParam1
892  *
893  * 3.
894  * ... nParam1 ... nParam2 ... nParagraph ...
895  * [xxxxxxxxxxx]___________|____________
896  * becomes
897  * ___________|____________[xxxxxxxxxxx]
898  *
899  * tail is nParam2 - nParam1
900  */
901 
902  // sort nParagraph, nParam1 and nParam2 in ascending order, calc range
903  if( nMiddle < nFirst )
904  {
905  ::std::swap(nFirst, nMiddle);
906  }
907  else if( nMiddle < nLast )
908  {
909  nLast = nLast + nMiddle - nFirst;
910  }
911  else
912  {
913  ::std::swap(nMiddle, nLast);
914  nLast = nLast + nMiddle - nFirst;
915  }
916 
917  if( nFirst < nParas && nMiddle < nParas && nLast < nParas )
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 
945  namespace {
946 
947  // functor for sending child events (no stand-alone function, they are maybe not inlined)
948  class AccessibleTextHelper_ChildrenTextChanged
949  {
950  public:
951  void operator()( ::accessibility::AccessibleEditableTextPara& rPara )
952  {
953  rPara.TextChanged();
954  }
955  };
956 
962  class AccessibleTextHelper_QueueFunctor
963  {
964  public:
965  AccessibleTextHelper_QueueFunctor() :
966  mnParasChanged( 0 ),
967  mnParaIndex(-1),
969  {}
970  void operator()( const SfxHint* pEvent )
971  {
972  if( pEvent &&
973  mnParasChanged != -1 )
974  {
975  // determine hint type
976  const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent );
977  const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent );
978 
979  if( !pEditSourceHint && pTextHint &&
980  (pTextHint->GetId() == SfxHintId::TextParaInserted ||
981  pTextHint->GetId() == SfxHintId::TextParaRemoved ) )
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  }
995  }
996 
1002  sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; }
1008  sal_Int32 GetParaIndex() const { return mnParaIndex; }
1013  SfxHintId GetHintId() const { return mnHintId; }
1014 
1015  private:
1019  sal_Int32 mnParasChanged;
1021  sal_Int32 mnParaIndex;
1024  };
1025 
1026  }
1027 
1029  {
1030  // inspect queue for paragraph insert/remove events. If there
1031  // is exactly _one_ of those in the queue, and the number of
1032  // paragraphs has changed by exactly one, use that event to
1033  // determine a priori which paragraph was added/removed. This
1034  // is necessary, since I must sync right here with the
1035  // EditEngine state (number of paragraphs etc.), since I'm
1036  // potentially sending listener events right away.
1037  AccessibleTextHelper_QueueFunctor aFunctor;
1038  maEventQueue.ForEach( aFunctor );
1039 
1040  const sal_Int32 nNewParas( GetTextForwarder().GetParagraphCount() );
1041  const sal_Int32 nCurrParas( maParaManager.GetNum() );
1042 
1043  // whether every paragraph already is updated (no need to
1044  // repeat that later on, e.g. for PARA_MOVED events)
1045  bool bEverythingUpdated( false );
1046 
1047  if( labs( nNewParas - nCurrParas ) == 1 &&
1048  aFunctor.GetNumberOfParasChanged() == 1 )
1049  {
1050  // #103483# Exactly one paragraph added/removed. This is
1051  // the normal case, optimize event handling here.
1052 
1053  if( aFunctor.GetHintId() == SfxHintId::TextParaInserted )
1054  {
1055  // update num of paras
1056  maParaManager.SetNum( nNewParas );
1057 
1058  // release everything from the insertion position until the end
1059  maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1060 
1061  // TODO: Clarify whether this behaviour _really_ saves
1062  // anybody anything!
1063  // update children, _don't_ broadcast
1064  UpdateVisibleChildren( false );
1065  UpdateBoundRect();
1066 
1067  // send insert event
1068  // #109864# Enforce creation of this paragraph
1069  try
1070  {
1071  GotPropertyEvent( uno::makeAny( getAccessibleChild( aFunctor.GetParaIndex() -
1073  AccessibleEventId::CHILD );
1074  }
1075  catch( const uno::Exception& )
1076  {
1077  OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph");
1078  }
1079  }
1080  else if( aFunctor.GetHintId() == SfxHintId::TextParaRemoved )
1081  {
1082  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
1083  ::std::advance( begin, aFunctor.GetParaIndex() );
1084  ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
1085  ::std::advance( end, 1 );
1086 
1087  // #i61812# remember para to be removed for later notification
1088  // AFTER the new state is applied (that after the para got removed)
1089  ::uno::Reference< XAccessible > xPara(begin->first.get().get());
1090 
1091  // release everything from the remove position until the end
1092  maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
1093 
1094  // update num of paras
1095  maParaManager.SetNum( nNewParas );
1096 
1097  // TODO: Clarify whether this behaviour _really_ saves
1098  // anybody anything!
1099  // update children, _don't_ broadcast
1100  UpdateVisibleChildren( false );
1101  UpdateBoundRect();
1102 
1103  // #i61812# notification for removed para
1104  if (xPara.is())
1105  FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny( xPara) );
1106  }
1107 #ifdef DBG_UTIL
1108  else
1109  OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue() invalid hint id");
1110 #endif
1111  }
1112  else if( nNewParas != nCurrParas )
1113  {
1114  // release all paras
1115  maParaManager.Release(0, nCurrParas);
1116 
1117  // update num of paras
1118  maParaManager.SetNum( nNewParas );
1119 
1120  // #109864# create from scratch, don't broadcast
1121  UpdateVisibleChildren( false );
1122  UpdateBoundRect();
1123 
1124  // number of paragraphs somehow changed - but we have no
1125  // chance determining how. Thus, throw away everything and
1126  // create from scratch.
1127  // (child events should be broadcast after the changes are done...)
1128  FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
1129 
1130  // no need for further updates later on
1131  bEverythingUpdated = true;
1132  }
1133 
1134  while( !maEventQueue.IsEmpty() )
1135  {
1136  ::std::unique_ptr< SfxHint > pHint( maEventQueue.PopFront() );
1137  if (pHint)
1138  {
1139  const SfxHint& rHint = *pHint;
1140 
1141  // Note, if you add events here, you need to update the AccessibleTextEventQueue::Append
1142  // code, because only the events we process here, are actually queued there.
1143 
1144  try
1145  {
1146 
1147  if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1148  {
1149  const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1150 
1151  switch( pSdrHint->GetKind() )
1152  {
1154  {
1155  if(!IsActive())
1156  {
1157  break;
1158  }
1159  // change children state
1160  maParaManager.SetActive();
1161 
1162  // per definition, edit mode text has the focus
1163  SetFocus( true );
1164  break;
1165  }
1166 
1167  case SdrHintKind::EndEdit:
1168  {
1169  // focused child now loses focus
1170  ESelection aSelection;
1171  if( GetEditViewForwarder().GetSelection( aSelection ) )
1172  SetChildFocus( aSelection.nEndPara, false );
1173 
1174  // change children state
1175  maParaManager.SetActive( false );
1176 
1179  break;
1180  }
1181  default:
1182  break;
1183  }
1184  }
1185  else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1186  {
1187  switch( pEditSourceHint->GetId() )
1188  {
1189  case SfxHintId::EditSourceParasMoved:
1190  {
1191  DBG_ASSERT( pEditSourceHint->GetStartValue() < GetTextForwarder().GetParagraphCount() &&
1192  pEditSourceHint->GetEndValue() < GetTextForwarder().GetParagraphCount(),
1193  "AccessibleTextHelper_Impl::NotifyHdl: Invalid notification");
1194 
1195  if( !bEverythingUpdated )
1196  {
1197  ParagraphsMoved(pEditSourceHint->GetStartValue(),
1198  pEditSourceHint->GetValue(),
1199  pEditSourceHint->GetEndValue());
1200 
1201  // in all cases, check visibility afterwards.
1203  }
1204  break;
1205  }
1206 
1207  case SfxHintId::EditSourceSelectionChanged:
1208  // notify listeners
1209  try
1210  {
1211  UpdateSelection();
1212  }
1213  // maybe we're not in edit mode (this is not an error)
1214  catch( const uno::Exception& ) {}
1215  break;
1216  default: break;
1217  }
1218  }
1219  else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1220  {
1221  const sal_Int32 nParas = GetTextForwarder().GetParagraphCount();
1222 
1223  switch( pTextHint->GetId() )
1224  {
1225  case SfxHintId::TextModified:
1226  {
1227  // notify listeners
1228  sal_Int32 nPara( pTextHint->GetValue() );
1229 
1230  // #108900# Delegate change event to children
1231  AccessibleTextHelper_ChildrenTextChanged aNotifyChildrenFunctor;
1232 
1233  if( nPara == EE_PARA_ALL )
1234  {
1235  // #108900# Call every child
1236  ::std::for_each( maParaManager.begin(), maParaManager.end(),
1237  AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1238  }
1239  else
1240  if( nPara < nParas )
1241  {
1242  // #108900# Call child at index nPara
1243  ::std::for_each( maParaManager.begin()+nPara, maParaManager.begin()+nPara+1,
1244  AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) );
1245  }
1246  break;
1247  }
1248 
1249  case SfxHintId::TextParaInserted:
1250  // already happened above
1251  break;
1252 
1253  case SfxHintId::TextParaRemoved:
1254  // already happened above
1255  break;
1256 
1257  case SfxHintId::TextHeightChanged:
1258  // visibility changed, done below
1259  break;
1260 
1261  case SfxHintId::TextViewScrolled:
1262  // visibility changed, done below
1263  break;
1264  default: break;
1265  }
1266 
1267  // in all cases, check visibility afterwards.
1269  UpdateBoundRect();
1270  }
1271  else if ( dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1272  {
1273  // just check visibility
1275  UpdateBoundRect();
1276  }
1277  // it's VITAL to keep the SfxSimpleHint last! It's the base of some classes above!
1278  else if( rHint.GetId() == SfxHintId::Dying)
1279  {
1280  // edit source is dying under us, become defunc then
1281  try
1282  {
1283  // make edit source inaccessible
1284  // Note: cannot destroy it here, since we're called from there!
1286  }
1287  catch( const uno::Exception& ) {}
1288  }
1289  }
1290  catch( const uno::Exception& )
1291  {
1292  DBG_UNHANDLED_EXCEPTION("svx");
1293  }
1294  }
1295  }
1296  }
1297 
1299  {
1300  // precondition: solar mutex locked
1302 
1303  // precondition: not in a recursion
1304  if( mbInNotify )
1305  return;
1306 
1307  mbInNotify = true;
1308 
1309  try
1310  {
1311  // Process notification event, arranged in order of likelihood of
1312  // occurrence to avoid unnecessary dynamic_cast. Note that
1313  // SvxEditSourceHint is derived from TextHint, so has to be checked
1314  // before that.
1315  if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
1316  {
1317  const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint );
1318  // process drawing layer events right away, if not
1319  // within an open EE notification frame. Otherwise,
1320  // event processing would be delayed until next EE
1321  // notification sequence.
1322  maEventQueue.Append( *pSdrHint );
1323  }
1324  else if( const SvxViewChangedHint* pViewHint = dynamic_cast<const SvxViewChangedHint*>( &rHint ) )
1325  {
1326  // process visibility right away, if not within an
1327  // open EE notification frame. Otherwise, event
1328  // processing would be delayed until next EE
1329  // notification sequence.
1330  maEventQueue.Append( *pViewHint );
1331  }
1332  else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
1333  {
1334  // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1335  maEventQueue.Append( *pEditSourceHint );
1336  }
1337  else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
1338  {
1339  // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
1340  if(pTextHint->GetId() == SfxHintId::TextProcessNotifications)
1341  ProcessQueue();
1342  else
1343  maEventQueue.Append( *pTextHint );
1344  }
1345  // it's VITAL to keep the SfxHint last! It's the base of the classes above!
1346  else if( rHint.GetId() == SfxHintId::Dying )
1347  {
1348  // handle this event _at once_, because after that, objects are invalid
1349  // edit source is dying under us, become defunc then
1350  maEventQueue.Clear();
1351  try
1352  {
1353  // make edit source inaccessible
1354  // Note: cannot destroy it here, since we're called from there!
1356  }
1357  catch( const uno::Exception& ) {}
1358  }
1359  }
1360  catch( const uno::Exception& )
1361  {
1362  DBG_UNHANDLED_EXCEPTION("svx");
1363  mbInNotify = false;
1364  }
1365 
1366  mbInNotify = false;
1367  }
1368 
1370  {
1371  if( getNotifierClientId() != -1 )
1372  {
1373  try
1374  {
1375  // #106234# Unregister from EventNotifier
1377  SAL_INFO("svx", "disposed ID: " << mnNotifierClientId );
1378  }
1379  catch( const uno::Exception& ) {}
1380 
1381  mnNotifierClientId = -1;
1382  }
1383 
1384  try
1385  {
1386  // dispose children
1387  maParaManager.Dispose();
1388  }
1389  catch( const uno::Exception& ) {}
1390 
1391  // quit listen on stale edit source
1392  if( maEditSource.IsValid() )
1393  EndListening( maEditSource.GetBroadcaster() );
1394 
1395  // clear references
1396  maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() );
1397  mxFrontEnd = nullptr;
1398  }
1399 
1400  void AccessibleTextHelper_Impl::FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const
1401  {
1402  // -- object locked --
1403  AccessibleEventObject aEvent;
1404  {
1405  osl::MutexGuard aGuard(maMutex);
1406 
1407  DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper::FireEvent: no event source set");
1408 
1409  if (mxFrontEnd.is())
1410  aEvent = AccessibleEventObject(mxFrontEnd->getAccessibleContext(), nEventId,
1411  rNewValue, rOldValue);
1412  else
1413  aEvent = AccessibleEventObject(uno::Reference<uno::XInterface>(), nEventId,
1414  rNewValue, rOldValue);
1415 
1416  // no locking necessary, FireEvent internally copies listeners
1417  // if someone removes/adds in between Further locking,
1418  // actually, might lead to deadlocks, since we're calling out
1419  // of this object
1420  }
1421  // -- until here --
1422 
1423  FireEvent(aEvent);
1424  }
1425 
1426  void AccessibleTextHelper_Impl::FireEvent( const AccessibleEventObject& rEvent ) const
1427  {
1428  // #102261# Call global queue for focus events
1429  if( rEvent.EventId == AccessibleStateType::FOCUSED )
1431 
1432  // #106234# Delegate to EventNotifier
1434  rEvent );
1435  }
1436 
1437  // XAccessibleContext
1439  {
1441  }
1442 
1443  uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int32 i )
1444  {
1445  i -= GetStartIndex();
1446 
1447  if( 0 > i || i >= getAccessibleChildCount() ||
1448  GetTextForwarder().GetParagraphCount() <= i )
1449  {
1450  throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd);
1451  }
1452 
1453  DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set");
1454 
1455  if( mxFrontEnd.is() )
1456  return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first;
1457  else
1458  return nullptr;
1459  }
1460 
1461  void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1462  {
1463  if( getNotifierClientId() != -1 )
1465  }
1466 
1467  void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1468  {
1469  if( getNotifierClientId() != -1 )
1470  {
1471  const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener );
1472  if ( !nListenerCount )
1473  {
1474  // no listeners anymore
1475  // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client),
1476  // and at least to us not firing any events anymore, in case somebody calls
1477  // NotifyAccessibleEvent, again
1479  mnNotifierClientId = -1;
1481  }
1482  }
1483  }
1484 
1485  uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint )
1486  {
1487  // make given position relative
1488  if( !mxFrontEnd.is() )
1489  throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1490 
1491  uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext();
1492 
1493  if( !xFrontEndContext.is() )
1494  throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd );
1495 
1496  uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW );
1497 
1498  // #103862# No longer need to make given position relative
1499  Point aPoint( _aPoint.X, _aPoint.Y );
1500 
1501  // respect EditEngine offset to surrounding shape/cell
1502  aPoint -= GetOffset();
1503 
1504  // convert to EditEngine coordinate system
1505  SvxTextForwarder& rCacheTF = GetTextForwarder();
1506  Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) );
1507 
1508  // iterate over all visible children (including those not yet created)
1509  sal_Int32 nChild;
1510  for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild )
1511  {
1512  DBG_ASSERT(nChild >= 0,
1513  "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow");
1514 
1515  tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) );
1516 
1517  if( aParaBounds.IsInside( aLogPoint ) )
1518  return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() );
1519  }
1520 
1521  // found none
1522  return nullptr;
1523  }
1524 
1525 
1526  // AccessibleTextHelper implementation (simply forwards to impl)
1527 
1528  AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) :
1529  mpImpl( new AccessibleTextHelper_Impl() )
1530  {
1531  SolarMutexGuard aGuard;
1532 
1533  SetEditSource( std::move(pEditSource) );
1534  }
1535 
1537  {
1538  }
1539 
1541  {
1542 #ifdef DBG_UTIL
1543  mpImpl->CheckInvariants();
1544 
1545  const SvxEditSource& aEditSource = mpImpl->GetEditSource();
1546 
1547  mpImpl->CheckInvariants();
1548 
1549  return aEditSource;
1550 #else
1551  return mpImpl->GetEditSource();
1552 #endif
1553  }
1554 
1555  void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
1556  {
1557 #ifdef DBG_UTIL
1558  // precondition: solar mutex locked
1560 
1561  mpImpl->CheckInvariants();
1562 #endif
1563 
1564  mpImpl->SetEditSource( std::move(pEditSource) );
1565 
1566 #ifdef DBG_UTIL
1567  mpImpl->CheckInvariants();
1568 #endif
1569  }
1570 
1571  void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface )
1572  {
1573 #ifdef DBG_UTIL
1574  mpImpl->CheckInvariants();
1575 #endif
1576 
1577  mpImpl->SetEventSource( rInterface );
1578 
1579 #ifdef DBG_UTIL
1580  mpImpl->CheckInvariants();
1581 #endif
1582  }
1583 
1584  void AccessibleTextHelper::SetFocus( bool bHaveFocus )
1585  {
1586 #ifdef DBG_UTIL
1587  // precondition: solar mutex locked
1589 
1590  mpImpl->CheckInvariants();
1591 #endif
1592 
1593  mpImpl->SetFocus( bHaveFocus );
1594 
1595 #ifdef DBG_UTIL
1596  mpImpl->CheckInvariants();
1597 #endif
1598  }
1599 
1601  {
1602 #ifdef DBG_UTIL
1603  mpImpl->CheckInvariants();
1604 
1605  bool bRet( mpImpl->HaveFocus() );
1606 
1607  mpImpl->CheckInvariants();
1608 
1609  return bRet;
1610 #else
1611  return mpImpl->HaveFocus();
1612 #endif
1613  }
1614 
1615  void AccessibleTextHelper::SetOffset( const Point& rPoint )
1616  {
1617 #ifdef DBG_UTIL
1618  // precondition: solar mutex locked
1620 
1621  mpImpl->CheckInvariants();
1622 #endif
1623 
1624  mpImpl->SetOffset( rPoint );
1625 
1626 #ifdef DBG_UTIL
1627  mpImpl->CheckInvariants();
1628 #endif
1629  }
1630 
1631  void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset )
1632  {
1633 #ifdef DBG_UTIL
1634  // precondition: solar mutex locked
1636 
1637  mpImpl->CheckInvariants();
1638 #endif
1639 
1640  mpImpl->SetStartIndex( nOffset );
1641 
1642 #ifdef DBG_UTIL
1643  mpImpl->CheckInvariants();
1644 #endif
1645  }
1646 
1648  {
1649 #ifdef DBG_UTIL
1650  mpImpl->CheckInvariants();
1651 
1652  sal_Int32 nOffset = mpImpl->GetStartIndex();
1653 
1654  mpImpl->CheckInvariants();
1655 
1656  return nOffset;
1657 #else
1658  return mpImpl->GetStartIndex();
1659 #endif
1660  }
1661 
1662  void AccessibleTextHelper::SetAdditionalChildStates( const VectorOfStates& rChildStates )
1663  {
1664  mpImpl->SetAdditionalChildStates( rChildStates );
1665  }
1666 
1668  {
1669 #ifdef DBG_UTIL
1670  // precondition: solar mutex locked
1672 
1673  mpImpl->CheckInvariants();
1674 #endif
1675 
1676  mpImpl->UpdateVisibleChildren();
1677  mpImpl->UpdateBoundRect();
1678 
1679  mpImpl->UpdateSelection();
1680 
1681 #ifdef DBG_UTIL
1682  mpImpl->CheckInvariants();
1683 #endif
1684  }
1685 
1687  {
1688  // As Dispose calls ShutdownEditSource, which in turn
1689  // deregisters as listener on the edit source, have to lock
1690  // here
1691  SolarMutexGuard aGuard;
1692 
1693 #ifdef DBG_UTIL
1694  mpImpl->CheckInvariants();
1695 #endif
1696 
1697  mpImpl->Dispose();
1698 
1699 #ifdef DBG_UTIL
1700  mpImpl->CheckInvariants();
1701 #endif
1702  }
1703 
1704  // XAccessibleContext
1706  {
1707  SolarMutexGuard aGuard;
1708 
1709 #ifdef DBG_UTIL
1710  mpImpl->CheckInvariants();
1711 
1712  sal_Int32 nRet = mpImpl->getAccessibleChildCount();
1713 
1714  mpImpl->CheckInvariants();
1715 
1716  return nRet;
1717 #else
1718  return mpImpl->getAccessibleChildCount();
1719 #endif
1720  }
1721 
1722  uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int32 i )
1723  {
1724  SolarMutexGuard aGuard;
1725 
1726 #ifdef DBG_UTIL
1727  mpImpl->CheckInvariants();
1728 
1729  uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i );
1730 
1731  mpImpl->CheckInvariants();
1732 
1733  return xRet;
1734 #else
1735  return mpImpl->getAccessibleChild( i );
1736 #endif
1737  }
1738 
1739  void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1740  {
1741 #ifdef DBG_UTIL
1742  mpImpl->CheckInvariants();
1743 
1744  mpImpl->addAccessibleEventListener( xListener );
1745 
1746  mpImpl->CheckInvariants();
1747 #else
1748  mpImpl->addAccessibleEventListener( xListener );
1749 #endif
1750  }
1751 
1752  void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener )
1753  {
1754 #ifdef DBG_UTIL
1755  mpImpl->CheckInvariants();
1756 
1757  mpImpl->removeAccessibleEventListener( xListener );
1758 
1759  mpImpl->CheckInvariants();
1760 #else
1761  mpImpl->removeAccessibleEventListener( xListener );
1762 #endif
1763  }
1764 
1765  // XAccessibleComponent
1766  uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint )
1767  {
1768  SolarMutexGuard aGuard;
1769 
1770 #ifdef DBG_UTIL
1771  mpImpl->CheckInvariants();
1772 
1773  uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint );
1774 
1775  mpImpl->CheckInvariants();
1776 
1777  return xChild;
1778 #else
1779  return mpImpl->getAccessibleAtPoint( aPoint );
1780 #endif
1781  }
1782 
1783 } // end of namespace accessibility
1784 
1785 
1786 /* 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
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)
const std::unique_ptr< AccessibleTextHelper_Impl > mpImpl
#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