/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
template < typename first_type, typename second_type > static ::std::pair< first_type, second_type > makeSortedPair( first_type first,
second_type second )
{ if( first > second ) return ::std::make_pair( second, first ); else return ::std::make_pair( first, second );
}
class AccessibleTextHelper_Impl : public SfxListener
{ public: // receive pointer to our frontend class and view window
AccessibleTextHelper_Impl(); virtual ~AccessibleTextHelper_Impl() override;
void SetOffset( const Point& );
Point GetOffset() const
{
std::scoped_lock aGuard( maMutex ); Point aPoint( maOffset ); return aPoint;
}
void SetStartIndex( sal_Int32 nOffset );
sal_Int32 GetStartIndex() const
{ // Strictly correct only with locked solar mutex, // but // here we rely on the fact that sal_Int32 access is // atomic return mnStartIndex;
}
// lock solar mutex before
SvxTextForwarder& GetTextForwarder() const; // lock solar mutex before
SvxViewForwarder& GetViewForwarder() const; // lock solar mutex before
SvxEditViewForwarder& GetEditViewForwarder() const;
// are we in edit mode? bool IsActive() const;
// our frontend class (the one implementing the actual // interface). That's not necessarily the one containing the impl // pointer!
uno::Reference< XAccessible > mxFrontEnd;
// a wrapper for the text forwarders (guarded by solar mutex) mutable SvxEditSourceAdapter maEditSource;
// store last selection (to correctly report selection changes, guarded by solar mutex)
ESelection maLastSelection;
// cache range of visible children (guarded by solar mutex)
sal_Int32 mnFirstVisibleChild;
sal_Int32 mnLastVisibleChild;
// offset to add to all our children (unguarded, relying on // the fact that sal_Int32 access is atomic)
sal_Int32 mnStartIndex;
// the object handling our children (guarded by solar mutex)
::accessibility::AccessibleParaManager maParaManager;
// Queued events from Notify() (guarded by solar mutex)
AccessibleTextEventQueue maEventQueue;
// spin lock to prevent notify in notify (guarded by solar mutex) bool mbInNotify;
// whether the object or its children has the focus set (guarded by solar mutex) bool mbGroupHasFocus;
// whether we (this object) has the focus set (guarded by solar mutex) bool mbThisHasFocus;
mutable std::mutex maMutex;
/// our current offset to the containing shape/cell (guarded by maMutex)
Point maOffset;
/// client Id from AccessibleEventNotifier
comphelper::AccessibleEventNotifier::TClientId mnNotifierClientId;
};
AccessibleTextHelper_Impl::AccessibleTextHelper_Impl() :
maLastSelection( ESelection::AtEnd() ),
mnFirstVisibleChild( -1 ),
mnLastVisibleChild( -2 ),
mnStartIndex( 0 ),
mbInNotify( false ),
mbGroupHasFocus( false ),
mbThisHasFocus( false ),
maOffset(0,0), // well, that's strictly exception safe, though not really // robust. We rely on the fact that this member is constructed // last, and that the constructor body is empty, thus no // chance for exceptions once the Id is fetched. Nevertheless, // normally should employ RAII here...
mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient())
{
SAL_INFO("svx", "received ID: " << mnNotifierClientId );
}
// notify all affected paragraphs (TODO: may be suboptimal, // since some paragraphs might stay selected) if (maLastSelection.start.nPara != EE_PARA_MAX)
{ // Did the caret move from one paragraph to another? // #100530# no caret events if not focused. if( mbGroupHasFocus &&
maLastSelection.end.nPara != aSelection.end.nPara )
{ if (maLastSelection.end.nPara < maParaManager.GetNum())
{
maParaManager.FireEvent( ::std::min( maLastSelection.end.nPara, nMaxValidParaIndex ),
::std::min( maLastSelection.end.nPara, nMaxValidParaIndex )+1,
AccessibleEventId::CARET_CHANGED,
uno::Any(static_cast<sal_Int32>(-1)),
uno::Any(maLastSelection.end.nIndex) );
}
SAL_INFO( "svx", "caret changed, Object: " << this << ", New pos: "
<< aSelection.end.nIndex << ", Old pos: "
<< maLastSelection.end.nIndex << ", New para: "
<< aSelection.end.nPara << ", Old para: "
<< maLastSelection.end.nPara);
// #108947# Sort new range before calling FireEvent
::std::pair<sal_Int32, sal_Int32> sortedSelection(
makeSortedPair(::std::min( aSelection.start.nPara, nMaxValidParaIndex ),
::std::min( aSelection.end.nPara, nMaxValidParaIndex ) ) );
// #108947# Sort last range before calling FireEvent
::std::pair<sal_Int32, sal_Int32> sortedLastSelection(
makeSortedPair(::std::min( maLastSelection.start.nPara, nMaxValidParaIndex ),
::std::min( maLastSelection.end.nPara, nMaxValidParaIndex ) ) );
// event TEXT_SELECTION_CHANGED has to be submitted. (#i27299#) const sal_Int16 nTextSelChgEventId =
AccessibleEventId::TEXT_SELECTION_CHANGED; // #107037# notify selection change if (maLastSelection.start.nPara == EE_PARA_MAX)
{ // last selection is undefined // use method <ESelection::HasRange()> (#i27299#) if ( aSelection.HasRange() )
{ // selection was undefined, now is on
maParaManager.FireEvent( sortedSelection.first,
sortedSelection.second+1,
nTextSelChgEventId );
}
} else
{ // last selection is valid // use method <ESelection::HasRange()> (#i27299#) if ( maLastSelection.HasRange() &&
!aSelection.HasRange() )
{ // selection was on, now is empty
maParaManager.FireEvent( sortedLastSelection.first,
sortedLastSelection.second+1,
nTextSelChgEventId );
} // use method <ESelection::HasRange()> (#i27299#) elseif( !maLastSelection.HasRange() &&
aSelection.HasRange() )
{ // selection was empty, now is on
maParaManager.FireEvent( sortedSelection.first,
sortedSelection.second+1,
nTextSelChgEventId );
} // no event TEXT_SELECTION_CHANGED event, if new and // last selection are empty. (#i27299#) elseif ( maLastSelection.HasRange() &&
aSelection.HasRange() )
{ // use sorted last and new selection
ESelection aTmpLastSel( maLastSelection );
aTmpLastSel.Adjust();
ESelection aTmpSel( aSelection );
aTmpSel.Adjust(); // first submit event for new and changed selection
sal_Int32 nPara = aTmpSel.start.nPara; for ( ; nPara <= aTmpSel.end.nPara; ++nPara )
{ if ( nPara < aTmpLastSel.start.nPara ||
nPara > aTmpLastSel.end.nPara )
{ // new selection on paragraph <nPara>
maParaManager.FireEvent( nPara,
nTextSelChgEventId );
} else
{ // check for changed selection on paragraph <nPara> const sal_Int32 nParaStartPos =
nPara == aTmpSel.start.nPara
? aTmpSel.start.nIndex : 0; const sal_Int32 nParaEndPos =
nPara == aTmpSel.end.nPara
? aTmpSel.end.nIndex : -1; const sal_Int32 nLastParaStartPos =
nPara == aTmpLastSel.start.nPara
? aTmpLastSel.start.nIndex : 0; const sal_Int32 nLastParaEndPos =
nPara == aTmpLastSel.end.nPara
? aTmpLastSel.end.nIndex : -1; if ( nParaStartPos != nLastParaStartPos ||
nParaEndPos != nLastParaEndPos )
{
maParaManager.FireEvent(
nPara, nTextSelChgEventId );
}
}
} // second submit event for 'old' selections
nPara = aTmpLastSel.start.nPara; for ( ; nPara <= aTmpLastSel.end.nPara; ++nPara )
{ if ( nPara < aTmpSel.start.nPara ||
nPara > aTmpSel.end.nPara )
{
maParaManager.FireEvent( nPara,
nTextSelChgEventId );
}
}
}
}
maLastSelection = aSelection;
} // no selection? no update actions catch( const uno::RuntimeException& ) {}
}
void AccessibleTextHelper_Impl::ShutdownEditSource()
{ // This should only be called with solar mutex locked, i.e. from the main office thread
// This here is somewhat clumsy: As soon as our children have // a NULL EditSource (maParaManager.SetEditSource()), they // enter the disposed state and cannot be reanimated. Thus, it // is unavoidable and a hard requirement to let go and create // from scratch each and every child.
// invalidate children
maParaManager.Dispose();
maParaManager.SetNum(0);
// lost all children if( mxFrontEnd.is() )
FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
// quit listen on stale edit source if( maEditSource.IsValid() )
EndListening( maEditSource.GetBroadcaster() );
void AccessibleTextHelper_Impl::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource )
{ // This should only be called with solar mutex locked, i.e. from the main office thread
// shutdown old edit source
ShutdownEditSource();
// set new edit source
maEditSource.SetEditSource( std::move(pEditSource) );
// init child vector to the current child count if( maEditSource.IsValid() )
{
maParaManager.SetNum( GetTextForwarder().GetParagraphCount() );
// listen on new edit source
StartListening( maEditSource.GetBroadcaster() );
UpdateVisibleChildren();
}
}
void AccessibleTextHelper_Impl::SetOffset( const Point& rPoint )
{ // guard against non-atomic access to maOffset data structure
{
std::scoped_lock aGuard( maMutex );
maOffset = rPoint;
}
maParaManager.SetEEOffset( rPoint );
// in all cases, check visibility afterwards.
UpdateVisibleChildren();
UpdateBoundRect();
}
// something failed - currently no children
mnFirstVisibleChild = -1;
mnLastVisibleChild = -2;
maParaManager.SetNum(0);
// lost all children if( bBroadcastEvents )
FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
}
}
void AccessibleTextHelper_Impl::UpdateBoundRect()
{ // send BOUNDRECT_CHANGED to affected children for(auto it = maParaManager.begin(); it != maParaManager.end(); ++it)
{
::accessibility::AccessibleParaManager::WeakChild& rChild = *it; // retrieve hard reference from weak one auto aHardRef( rChild.first.get() );
// functor for sending child events (no stand-alone function, they are maybe not inlined) class AccessibleTextHelper_LostChildEvent
{ public: explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {} voidoperator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara )
{ // retrieve hard reference from weak one auto aHardRef( rPara.first.get() );
// send CHILD_EVENT to affected children
::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
// TODO: maybe optimize here in the following way. If the // number of removed children exceeds a certain threshold, // use InvalidateFlags::Children
AccessibleTextHelper_LostChildEvent aFunctor( *this );
::std::for_each( begin, end, aFunctor );
maParaManager.Release(nFirst, nLast+1); // should be no need for UpdateBoundRect, since all affected children are cleared.
}
namespace {
// functor for sending child events (no stand-alone function, they are maybe not inlined) class AccessibleTextHelper_ChildrenTextChanged
{ public: voidoperator()( ::accessibility::AccessibleEditableTextPara& rPara )
{
rPara.TextChanged();
}
};
/** functor processing queue events
Reacts on SfxHintId::TextParaInserted/REMOVED events and stores their content
*/ class AccessibleTextHelper_QueueFunctor
{ public:
AccessibleTextHelper_QueueFunctor() :
mnParasChanged( 0 ),
mnParaIndex(-1),
mnHintId(SfxHintId::NONE)
{} voidoperator()( const SfxHint* pEvent )
{ if( !pEvent || mnParasChanged == -1 ) return;
// determine hint type const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent ); const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent );
/** Query number of paragraphs changed during queue processing.
@return number of changed paragraphs, -1 for "every paragraph changed"
*/
sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; } /** Query index of last added/removed paragraph
@return index of lastly added paragraphs, -1 for none added so far.
*/
sal_Int32 GetParaIndex() const { return mnParaIndex; } /** Query hint id of last interesting event
@return hint id of last interesting event (REMOVED/INSERTED).
*/
SfxHintId GetHintId() const { return mnHintId; }
private: /** number of paragraphs changed during queue processing. -1 for "every paragraph changed"
*/
sal_Int32 mnParasChanged; /// index of paragraph added/removed last
sal_Int32 mnParaIndex; /// TextHint ID (removed/inserted) of last interesting event
SfxHintId mnHintId;
};
}
void AccessibleTextHelper_Impl::ProcessQueue()
{ // inspect queue for paragraph insert/remove events. If there // is exactly _one_ of those in the queue, and the number of // paragraphs has changed by exactly one, use that event to // determine a priori which paragraph was added/removed. This // is necessary, since I must sync right here with the // EditEngine state (number of paragraphs etc.), since I'm // potentially sending listener events right away.
AccessibleTextHelper_QueueFunctor aFunctor;
maEventQueue.ForEach( aFunctor );
// send insert event // #109864# Enforce creation of this paragraph try
{
FireEvent(AccessibleEventId::CHILD, uno::Any(getAccessibleChild(aFunctor.GetParaIndex() -
mnFirstVisibleChild + GetStartIndex())));
} catch( const uno::Exception& )
{
OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph");
}
} elseif( aFunctor.GetHintId() == SfxHintId::TextParaRemoved )
{
::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin();
::std::advance( begin, aFunctor.GetParaIndex() );
::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin;
::std::advance( end, 1 );
// #i61812# remember para to be removed for later notification // AFTER the new state is applied (that after the para got removed)
rtl::Reference<AccessibleEditableTextPara> xPara(begin->first.get());
// release everything from the remove position until the end
maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas);
// update num of paras
maParaManager.SetNum( nNewParas );
// number of paragraphs somehow changed - but we have no // chance determining how. Thus, throw away everything and // create from scratch. // (child events should be broadcast after the changes are done...)
FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN);
// no need for further updates later on
bEverythingUpdated = true;
}
// Note, if you add events here, you need to update the AccessibleTextEventQueue::Append // code, because only the events we process here, are actually queued there.
// precondition: not in a recursion if( mbInNotify ) return;
mbInNotify = true;
try
{ // Process notification event, arranged in order of likelihood of // occurrence to avoid unnecessary dynamic_cast. Note that // SvxEditSourceHint is derived from TextHint, so has to be checked // before that. if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
{ const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint ); // process drawing layer events right away, if not // within an open EE notification frame. Otherwise, // event processing would be delayed until next EE // notification sequence.
maEventQueue.Append( *pSdrHint );
} elseif (rHint.GetId() == SfxHintId::SvxViewChanged)
{ const SvxViewChangedHint* pViewHint = static_cast<const SvxViewChangedHint*>(&rHint); // process visibility right away, if not within an // open EE notification frame. Otherwise, event // processing would be delayed until next EE // notification sequence.
maEventQueue.Append( *pViewHint );
} elseif( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) )
{ // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#)
maEventQueue.Append( *pEditSourceHint );
} elseif( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) )
{ // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#) if(pTextHint->GetId() == SfxHintId::TextProcessNotifications)
ProcessQueue(); else
maEventQueue.Append( *pTextHint );
} // it's VITAL to keep the SfxHint last! It's the base of the classes above! elseif( rHint.GetId() == SfxHintId::Dying )
{ // handle this event _at once_, because after that, objects are invalid // edit source is dying under us, become defunc then
maEventQueue.Clear(); try
{ // make edit source inaccessible // Note: cannot destroy it here, since we're called from there!
ShutdownEditSource();
} catch( const uno::Exception& ) {}
}
} catch( const uno::Exception& )
{
DBG_UNHANDLED_EXCEPTION("svx");
mbInNotify = false;
}
// no locking necessary, FireEvent internally copies listeners // if someone removes/adds in between Further locking, // actually, might lead to deadlocks, since we're calling out // of this object
} // -- until here --
if (mnNotifierClientId != 0)
::comphelper::AccessibleEventNotifier::addEvent(mnNotifierClientId, aEvent);
}
const sal_Int32 nListenerCount
= ::comphelper::AccessibleEventNotifier::removeEventListener(mnNotifierClientId, xListener); if ( !nListenerCount )
{ // no listeners anymore // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), // and at least to us not firing any events anymore, in case somebody calls // NotifyAccessibleEvent, again
::comphelper::AccessibleEventNotifier::TClientId nId = mnNotifierClientId;
mnNotifierClientId = 0;
::comphelper::AccessibleEventNotifier::revokeClient( nId );
}
}
uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint )
{ // make given position relative if( !mxFrontEnd.is() ) throw uno::RuntimeException(u"AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid"_ustr, mxFrontEnd );
// #103862# No longer need to make given position relative
Point aPoint( _aPoint.X, _aPoint.Y );
// respect EditEngine offset to surrounding shape/cell
aPoint -= GetOffset();
// convert to EditEngine coordinate system
SvxTextForwarder& rCacheTF = GetTextForwarder();
Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) );
// iterate over all visible children (including those not yet created)
sal_Int64 nChild; for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild )
{
DBG_ASSERT(nChild >= 0, "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow");
void AccessibleTextHelper::Dispose()
{ // As Dispose calls ShutdownEditSource, which in turn // deregisters as listener on the edit source, have to lock // here
SolarMutexGuard aGuard;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.