/* -*- 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 .
*/
class SfxFoundCacheArr_Impl
{
std::vector<SfxFoundCache_Impl> maData;
public:
SfxFoundCache_Impl& operator[] ( size_t i )
{ return maData[i];
}
size_t size() const
{ return maData.size();
}
void push_back( SfxFoundCache_Impl p )
{
maData.push_back(p);
}
};
class SfxBindings_Impl
{ public:
css::uno::Reference< css::frame::XDispatchRecorder > xRecorder;
css::uno::Reference< css::frame::XDispatchProvider > xProv;
std::unique_ptr<SfxWorkWindow> mxWorkWin;
SfxBindings* pSubBindings;
std::vector<std::unique_ptr<SfxStateCache>> pCaches; // One cache for each binding
std::size_t nCachedFunc1; // index for the last one called
std::size_t nCachedFunc2; // index for the second last called
std::size_t nMsgPos; // Message-Position relative the one to be updated bool bContextChanged; bool bMsgDirty; // Has a MessageServer been invalidated? bool bAllMsgDirty; // Has a MessageServer been invalidated? bool bAllDirty; // After InvalidateAll bool bCtrlReleased; // while EnterRegistrations
AutoTimer aAutoTimer { "sfx::SfxBindings aAutoTimer" }; // for volatile Slots bool bInUpdate; // for Assertions bool bInNextJob; // for Assertions bool bFirstRound; // First round in Update
sal_uInt16 nOwnRegLevel; // Counts the real Locks, except those of the Super Bindings
std::unordered_map< sal_uInt16, bool >
m_aInvalidateSlots; // store slots which are invalidated while in update
};
SfxBindings::SfxBindings()
: pImpl(new SfxBindings_Impl),
pDispatcher(nullptr),
nRegLevel(1) // first becomes 0, when the Dispatcher is set
pImpl->aAutoTimer.SetPriority(TaskPriority::DEFAULT_IDLE); // all caches are valid (no pending invalidate-job) // create the list of caches
pImpl->aAutoTimer.SetPriority(TaskPriority::HIGH_IDLE);
pImpl->aAutoTimer.SetInvokeHandler( LINK(this, SfxBindings, NextJob) );
}
SfxBindings::~SfxBindings()
/* [Description]
Destructor of the SfxBindings class. The one, for each <SfxApplication> existing Instance is automatically destroyed by the <SfxApplication> after the execution of <SfxApplication::Exit()>.
The still existing <SfxControllerItem> instances, which are registered by the SfxBindings instance, are automatically destroyed in the Destructor. These are usually the Floating-Toolboxen, Value-Sets etc. Arrays of SfxControllerItems may at this time no longer exist.
*/
{ // The SubBindings should not be locked!
pImpl->pSubBindings = nullptr;
void SfxBindings::HidePopups( bool bHide )
{ // Hide SfxChildWindows
DBG_ASSERT( pDispatcher, "HidePopups not allowed without dispatcher" ); if ( pImpl->mxWorkWin )
pImpl->mxWorkWin->HidePopups_Impl( bHide );
}
void SfxBindings::Update_Impl(SfxStateCache& rCache /*The up to date SfxStatusCache*/)
{ if (rCache.GetDispatch().is() && rCache.GetItemLink())
{
rCache.SetCachedState(true); if (!rCache.GetInternalController()) return;
}
if ( !pDispatcher ) return;
// gather together all with the same status method which are dirty
SfxDispatcher &rDispat = *pDispatcher; const SfxSlot *pRealSlot = nullptr; const SfxSlotServer* pMsgServer = nullptr;
SfxFoundCacheArr_Impl aFound;
std::optional<SfxItemSet> pSet = CreateSet_Impl(rCache, pRealSlot, &pMsgServer, aFound); bool bUpdated = false; if ( pSet )
{ // Query Status if ( rDispat.FillState_( *pMsgServer, *pSet, pRealSlot ) )
{ // Post Status for ( size_t nPos = 0; nPos < aFound.size(); ++nPos )
{ const SfxFoundCache_Impl& rFound = aFound[nPos];
sal_uInt16 nWhich = rFound.nWhichId; const SfxPoolItem *pItem = nullptr;
SfxItemState eState = pSet->GetItemState(nWhich, true, &pItem); if ( eState == SfxItemState::DEFAULT && SfxItemPool::IsWhich(nWhich) )
pItem = &pSet->Get(nWhich);
UpdateControllers_Impl( rFound, pItem, eState );
}
bUpdated = true;
}
void SfxBindings::SetState
( const SfxItemSet& rSet // status values to be set
)
{ // when locked then only invalidate if ( nRegLevel )
{
SfxItemIter aIter(rSet); for ( const SfxPoolItem *pItem = aIter.GetCurItem();
pItem;
pItem = aIter.NextItem() )
Invalidate( pItem->Which() );
} else
{ // Status may be accepted only if all slot-pointers are set if ( pImpl->bMsgDirty )
UpdateSlotServer_Impl();
// Iterate over the itemset, update if the slot bound //! Bug: Use WhichIter and possibly send VoidItems up
SfxItemIter aIter(rSet); for ( const SfxPoolItem *pItem = aIter.GetCurItem();
pItem;
pItem = aIter.NextItem() )
{
SfxStateCache* pCache =
GetStateCache( rSet.GetPool()->GetSlotId(pItem->Which()) ); if ( pCache )
{ // Update status if ( !pCache->IsControllerDirty() )
pCache->Invalidate(false);
pCache->SetState( SfxItemState::DEFAULT, pItem );
//! Not implemented: Updates from EnumSlots via master slots
}
}
}
}
void SfxBindings::SetState
( const SfxPoolItem& rItem // Status value to be set
)
{ if ( nRegLevel )
{
Invalidate( rItem.Which() );
} else
{ // Status may be accepted only if all slot-pointers are set if ( pImpl->bMsgDirty )
UpdateSlotServer_Impl();
//update if the slot bound
DBG_ASSERT( SfxItemPool::IsSlot( rItem.Which() ), "cannot set items with which-id" );
SfxStateCache* pCache = GetStateCache( rItem.Which() ); if ( pCache )
{ // Update Status if ( !pCache->IsControllerDirty() )
pCache->Invalidate(false);
pCache->SetState( SfxItemState::DEFAULT, &rItem );
//! Not implemented: Updates from EnumSlots via master slots
}
}
}
SfxStateCache* SfxBindings::GetAnyStateCache_Impl( sal_uInt16 nId )
{
SfxStateCache* pCache = GetStateCache( nId ); if ( !pCache && pImpl->pSubBindings ) return pImpl->pSubBindings->GetAnyStateCache_Impl( nId ); return pCache;
}
SfxStateCache* SfxBindings::GetStateCache
(
sal_uInt16 nId /* Slot-Id, which SfxStatusCache is to be found */
)
{ return GetStateCache(nId, nullptr);
}
SfxStateCache* SfxBindings::GetStateCache
(
sal_uInt16 nId, /* Slot-Id, which SfxStatusCache is to be found */
std::size_t * pPos /* NULL for instance the position from which the bindings are to be searched binary. Returns the position back for where the nId was found,
or where it was inserted. */
)
{ // is the specified function bound? const std::size_t nStart = ( pPos ? *pPos : 0 ); const std::size_t nPos = GetSlotPos( nId, nStart );
if ( nPos < pImpl->pCaches.size() &&
pImpl->pCaches[nPos]->GetId() == nId )
{ if ( pPos )
*pPos = nPos; return pImpl->pCaches[nPos].get();
} return nullptr;
}
void SfxBindings::InvalidateAll
( bool bWithMsg /* true Mark Slot Server as invalid
false Slot Server remains valid */
)
{
DBG_ASSERT( !pImpl->bInUpdate, "SfxBindings::Invalidate while in update" );
if ( pImpl->pSubBindings )
pImpl->pSubBindings->InvalidateAll( bWithMsg );
// everything is already set dirty or downing => nothing to do if ( !pDispatcher ||
( pImpl->bAllDirty && ( !bWithMsg || pImpl->bAllMsgDirty ) ) ||
SfxGetpApp()->IsDowning() )
{ return;
}
void SfxBindings::Invalidate
( const sal_uInt16* pIds /* numerically sorted NULL-terminated array of
slot IDs (individual, not as a couple!) */
)
{ if ( pImpl->bInUpdate )
{
sal_Int32 i = 0; while ( pIds[i] != 0 )
AddSlotToInvalidateSlotsMap_Impl( pIds[i++] );
if ( pImpl->pSubBindings )
pImpl->pSubBindings->Invalidate( pIds ); return;
}
if ( pImpl->pSubBindings )
pImpl->pSubBindings->Invalidate( pIds );
// everything is already set dirty or downing => nothing to do if ( !pDispatcher || pImpl->bAllDirty || SfxGetpApp()->IsDowning() ) return;
// Search binary in always smaller areas for ( std::size_t n = GetSlotPos(*pIds);
*pIds && n < pImpl->pCaches.size();
n = GetSlotPos(*pIds, n) )
{ // If SID is ever bound, then invalidate the cache
SfxStateCache *pCache = pImpl->pCaches[n].get(); if ( pCache->GetId() == *pIds )
pCache->Invalidate(false);
// Next SID if ( !*++pIds ) break;
assert( *pIds > *(pIds-1) );
}
// if not enticed to start update timer
pImpl->nMsgPos = 0; if ( !nRegLevel )
{
pImpl->aAutoTimer.Stop();
pImpl->aAutoTimer.SetTimeout(TIMEOUT_FIRST);
pImpl->aAutoTimer.Start();
}
}
void SfxBindings::InvalidateShell
( const SfxShell& rSh, /* <SfxShell> whose Slot-Ids should be
invalidated */ bool bDeep /* true also the SfxShell's inherited slot IDs are invalidated
false the inherited and not overridden Slot-Ids are
invalidated */ // for now always bDeep
)
{
DBG_ASSERT( !pImpl->bInUpdate, "SfxBindings::Invalidate while in update" );
if ( pImpl->pSubBindings )
pImpl->pSubBindings->InvalidateShell( rSh, bDeep );
if ( !pDispatcher || pImpl->bAllDirty || SfxGetpApp()->IsDowning() ) return;
// flush now already, it is done in GetShellLevel (rsh) anyway, // important so that is set correctly: pImpl-> ball(Msg)Dirty
pDispatcher->Flush();
if ((pImpl->bAllDirty && pImpl->bAllMsgDirty) || SfxGetpApp()->IsDowning())
{ // if the next one is anyway, then all the servers are collected return;
}
void SfxBindings::Invalidate
(
sal_uInt16 nId // Status value to be set
)
{ if ( pImpl->bInUpdate )
{
AddSlotToInvalidateSlotsMap_Impl( nId ); if ( pImpl->pSubBindings )
pImpl->pSubBindings->Invalidate( nId ); return;
}
if ( pImpl->pSubBindings )
pImpl->pSubBindings->Invalidate( nId );
if ( !pDispatcher || pImpl->bAllDirty || SfxGetpApp()->IsDowning() ) return;
void SfxBindings::Invalidate
(
sal_uInt16 nId, // Status value to be set bool bWithItem, // Clear StateCache? bool bWithMsg // Get new SlotServer?
)
{
DBG_ASSERT( !pImpl->bInUpdate, "SfxBindings::Invalidate while in update" );
if ( pImpl->pSubBindings )
pImpl->pSubBindings->Invalidate( nId, bWithItem, bWithMsg );
if ( SfxGetpApp()->IsDowning() ) return;
SfxStateCache* pCache = GetStateCache(nId); if ( !pCache ) return;
if ( bWithItem )
pCache->ClearCache();
pCache->Invalidate(bWithMsg);
// find the bound function
sal_uInt16 nId = rItem.GetId();
std::size_t nPos = GetSlotPos(nId);
SfxStateCache* pCache = (nPos < pImpl->pCaches.size()) ? pImpl->pCaches[nPos].get() : nullptr; if ( pCache && pCache->GetId() == nId )
{ if ( pCache->GetInternalController() == &rItem )
{
pCache->ReleaseInternalController();
} else
{ // is this the first binding in the list?
SfxControllerItem* pItem = pCache->GetItemLink(); if ( pItem == &rItem )
pCache->ChangeItemLink( rItem.GetItemLink() ); else
{ // search the binding in the list while ( pItem && pItem->GetItemLink() != &rItem )
pItem = pItem->GetItemLink();
// unlink it if it was found if ( pItem )
pItem->ChangeItemLink( rItem.GetItemLink() );
}
}
// was this the last controller? if ( pCache->GetItemLink() == nullptr && !pCache->GetInternalController() )
{
pImpl->bCtrlReleased = true;
}
}
// get SlotServer (Slot+ShellLevel) and Shell from cache
std::unique_ptr<SfxStateCache> xCache; if ( !pCache )
{ // Execution of non cached slots (Accelerators don't use Controllers) // slot is uncached, use SlotCache to handle external dispatch providers
xCache.reset(new SfxStateCache(nId));
pCache = xCache.get();
}
pCache->GetSlotServer( rDispatcher, pImpl->xProv ); // make pCache->GetDispatch() up to date if ( pCache->GetDispatch().is() )
{
SfxItemPool &rPool = GetDispatcher()->GetFrame()->GetObjectShell()->GetPool();
SfxRequest aReq( nId, nCallMode, rPool ); if( ppItems ) while( *ppItems )
aReq.AppendItem( **ppItems++ );
// cache binds to an external dispatch provider
sal_Int16 eRet = pCache->Dispatch( aReq.GetArgs(), nCallMode == SfxCallMode::SYNCHRON );
SfxPoolItem* pPoolItem(nullptr); if ( eRet == css::frame::DispatchResultState::DONTKNOW )
pPoolItem = new SfxVoidItem( nId ); else
pPoolItem = new SfxBoolItem( nId, eRet == css::frame::DispatchResultState::SUCCESS);
if ( SfxSlotKind::Attribute == pSlot->GetKind() )
{ // Which value has to be mapped for Attribute slots const sal_uInt16 nSlotId = pSlot->GetSlotId();
aReq.SetSlot( nSlotId ); if ( pSlot->IsMode(SfxSlotMode::TOGGLE) )
{ // The value is attached to a toggleable attribute (Bools)
sal_uInt16 nWhich = pSlot->GetWhich(rPool);
SfxItemSet aSet(rPool, nWhich, nWhich);
SfxStateFunc pFunc = pSlot->GetStateFnc();
(*pFunc)(pShell, aSet); const SfxPoolItem *pOldItem;
SfxItemState eState = aSet.GetItemState(nWhich, true, &pOldItem); if ( eState == SfxItemState::DISABLED ) return;
if ( pImpl->bAllMsgDirty )
{ if ( !nRegLevel )
{
pImpl->bContextChanged = false;
} else
pImpl->bContextChanged = true;
}
for (size_t i = 0; i < pImpl->pCaches.size(); ++i)
{ //GetSlotServer can modify pImpl->pCaches
pImpl->pCaches[i]->GetSlotServer(*pDispatcher, pImpl->xProv);
}
pImpl->bMsgDirty = pImpl->bAllMsgDirty = false;
Broadcast( SfxHint(SfxHintId::DocChanged) );
}
std::optional<SfxItemSet> SfxBindings::CreateSet_Impl
(
SfxStateCache& rCache, // in: Status-Cache from nId const SfxSlot*& pRealSlot, // out: RealSlot to nId const SfxSlotServer** pMsgServer, // out: Slot-Server to nId
SfxFoundCacheArr_Impl& rFound // out: List of Caches for Siblings
)
{
DBG_ASSERT( !pImpl->bMsgDirty, "CreateSet_Impl with dirty MessageServer" );
assert(pDispatcher);
const SfxSlotServer* pMsgSvr = rCache.GetSlotServer(*pDispatcher, pImpl->xProv); if (!pMsgSvr) return {};
pRealSlot = nullptr;
*pMsgServer = pMsgSvr;
sal_uInt16 nShellLevel = pMsgSvr->GetShellLevel();
SfxShell *pShell = pDispatcher->GetShell( nShellLevel ); if ( !pShell ) // rare GPF when browsing through update from Inet-Notify return {};
SfxItemPool &rPool = pShell->GetPool();
// get the status method, which is served by the rCache
SfxStateFunc pFnc = nullptr;
pRealSlot = pMsgSvr->GetSlot();
pFnc = pRealSlot->GetStateFnc();
// the RealSlot is always on
SfxFoundCache_Impl aFound(pRealSlot->GetWhich(rPool), pRealSlot, rCache);
rFound.push_back( aFound );
// Search through the bindings for slots served by the same function. This , // will only affect slots which are present in the found interface.
// The position of the Statecaches in StateCache-Array
std::size_t nCachePos = pImpl->nMsgPos; const SfxSlot *pSibling = pRealSlot->GetNextSlot();
// the Slots ODF and interfaces are linked in a circle while ( pSibling > pRealSlot )
{
SfxStateFunc pSiblingFnc=nullptr;
SfxStateCache *pSiblingCache =
GetStateCache( pSibling->GetSlotId(), &nCachePos );
// Is the slot cached ? if ( pSiblingCache )
{ const SfxSlotServer *pServ = pSiblingCache->GetSlotServer(*pDispatcher, pImpl->xProv); if ( pServ && pServ->GetShellLevel() == nShellLevel )
pSiblingFnc = pServ->GetSlot()->GetStateFnc();
}
// Does the slot have to be updated at all? bool bInsert = pSiblingCache && pSiblingCache->IsControllerDirty();
// It is not enough to ask for the same shell!! bool bSameMethod = pSiblingCache && pFnc == pSiblingFnc;
// Create a Set from the ranges
WhichRangesContainer ranges;
size_t i = 0; while ( i < rFound.size() )
{ const sal_uInt16 nWhich1 = rFound[i].nWhichId; // consecutive numbers for ( ; i < rFound.size()-1; ++i ) if ( rFound[i].nWhichId+1 != rFound[i+1].nWhichId ) break; const sal_uInt16 nWhich2 = rFound[i++].nWhichId;
ranges = ranges.MergeRange(nWhich1, nWhich2);
}
SfxItemSet aSet(rPool, std::move(ranges)); return aSet;
}
void SfxBindings::UpdateControllers_Impl
( const SfxFoundCache_Impl& rFound, // Cache, Slot, Which etc. const SfxPoolItem* pItem, // item to send to controller
SfxItemState eState // state of item
)
{
SfxStateCache& rCache = rFound.rCache; const SfxSlot* pSlot = rFound.pSlot;
DBG_ASSERT( !pSlot || rCache.GetId() == pSlot->GetSlotId(), "SID mismatch" );
// bound until now, the Controller to update the Slot. if (!rCache.IsControllerDirty()) return;
if ( SfxItemState::INVALID == eState )
{ // ambiguous
rCache.SetState( SfxItemState::INVALID, INVALID_POOL_ITEM );
} elseif ( SfxItemState::DEFAULT == eState &&
SfxItemPool::IsSlot(rFound.nWhichId) )
{ // no Status or Default but without Pool // tdf#162666 note that use DISABLED_POOL_ITEM needs to be // handled correctly in the cache, see comments there
rCache.SetState( SfxItemState::UNKNOWN, DISABLED_POOL_ITEM );
} elseif ( SfxItemState::DISABLED == eState )
rCache.SetState(SfxItemState::DISABLED, nullptr); else
rCache.SetState(SfxItemState::DEFAULT, pItem);
}
// at least 10 loops and further if more jobs are available but no input bool bPreEmptive = pTimer;
sal_uInt16 nLoops = 10;
pImpl->bInNextJob = true; const std::size_t nCount = pImpl->pCaches.size(); while ( pImpl->nMsgPos < nCount )
{ // iterate through the bound functions bool bJobDone = false; while ( !bJobDone )
{
SfxStateCache* pCache = pImpl->pCaches[pImpl->nMsgPos].get();
assert(pCache && "invalid SfxStateCache-position in job queue"); bool bWasDirty = pCache->IsControllerDirty(); if ( bWasDirty )
{
Update_Impl(*pCache);
DBG_ASSERT(nCount == pImpl->pCaches.size(), "Reschedule in StateChanged => buff");
}
// skip to next function binding
++pImpl->nMsgPos;
// keep job if it is not completed, but any input is available
bJobDone = pImpl->nMsgPos >= nCount; if ( bJobDone && pImpl->bFirstRound )
{
// Update of the preferred shell has been done, now may // also the others shells be updated
bJobDone = false;
pImpl->bFirstRound = false;
pImpl->nMsgPos = 0;
}
// check if this is the outer most level if ( ++nRegLevel == 1 )
{ // stop background-processing
pImpl->aAutoTimer.Stop();
// flush the cache
pImpl->nCachedFunc1 = 0;
pImpl->nCachedFunc2 = 0;
// Mark if the all of the Caches have disappeared.
pImpl->bCtrlReleased = false;
}
return nRegLevel;
}
void SfxBindings::LeaveRegistrations( std::string_view pFile, int nLine )
{
DBG_ASSERT( nRegLevel, "Leave without Enter" );
// Only when the SubBindings are still locked by the Superbindings, // remove this lock (i.e. if there are more locks than "real" ones) if ( pImpl->pSubBindings && pImpl->pSubBindings->nRegLevel > pImpl->pSubBindings->pImpl->nOwnRegLevel )
{ // Synchronize Bindings
pImpl->pSubBindings->nRegLevel = nRegLevel + pImpl->pSubBindings->pImpl->nOwnRegLevel;
// This LeaveRegistrations is not "real" for SubBindings
pImpl->pSubBindings->pImpl->nOwnRegLevel++;
pImpl->pSubBindings->LEAVEREGISTRATIONS();
}
pImpl->nOwnRegLevel--;
// check if this is the outer most level if ( --nRegLevel == 0 && SfxGetpApp() && !SfxGetpApp()->IsDowning() )
{ if ( pImpl->bContextChanged )
{
pImpl->bContextChanged = false;
}
SfxViewFrame* pFrame = pDispatcher->GetFrame();
// If possible remove unused Caches, for example prepare PlugInInfo if ( pImpl->bCtrlReleased )
{ for ( sal_uInt16 nCache = pImpl->pCaches.size(); nCache > 0; --nCache )
{ // Get Cache via css::sdbcx::Index
SfxStateCache *pCache = pImpl->pCaches[nCache-1].get();
// No interested Controller present if ( pCache->GetItemLink() == nullptr && !pCache->GetInternalController() )
{ // Remove Cache. Safety: first remove and then delete
pImpl->pCaches.erase(pImpl->pCaches.begin() + nCache - 1);
}
}
}
// Then test at the dispatcher to check if the returned items from // there are always DELETE_ON_IDLE, a copy of it has to be made in // order to allow for transition of ownership.
SfxPoolItemHolder aResult; const SfxItemState eState(pDispatcher->QueryState(nSlot, aResult));
if (SfxItemState::SET == eState)
{
DBG_ASSERT( aResult.getItem(), "SfxItemState::SET but no item!" ); if (aResult)
rpState.reset(aResult.getItem()->Clone());
} elseif (SfxItemState::DEFAULT == eState && aResult)
{
rpState.reset(aResult.getItem()->Clone());
}
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.