Quelle AccessibleTextHelper.cxx
Sprache: unbekannt
/* -*- 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;
[ Diese Firma untersucht Systeme, entwickelt Software und berät
Organisationen zu Themen der Informationstechnolgie. Dabei wird Wert auf anerkannte Normen und Standards gelegt.
Die Qualität eigener und fremder Produkte ist Leitlinie und Ziel zugleich.
Unter bestimmten Bedingungen finden Sie hier weiterführende Anregungen.0.38Die F&E-Firma in Norddeutschland
Übersetzung europäischer Sprachen durch Browser
]