diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sfx2/source/control/bindings.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sfx2/source/control/bindings.cxx')
-rw-r--r-- | sfx2/source/control/bindings.cxx | 1783 |
1 files changed, 1783 insertions, 0 deletions
diff --git a/sfx2/source/control/bindings.cxx b/sfx2/source/control/bindings.cxx new file mode 100644 index 000000000..4ea062315 --- /dev/null +++ b/sfx2/source/control/bindings.cxx @@ -0,0 +1,1783 @@ +/* -*- 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 . + */ + +#include <sal/config.h> + +#include <iomanip> + +#include <comphelper/servicehelper.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <svl/itempool.hxx> +#include <svl/itemiter.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <itemdel.hxx> + +//Includes below due to nInReschedule +#include <sfx2/bindings.hxx> +#include <sfx2/msg.hxx> +#include <statcach.hxx> +#include <sfx2/ctrlitem.hxx> +#include <sfx2/app.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/module.hxx> +#include <sfx2/request.hxx> +#include <workwin.hxx> +#include <unoctitm.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/msgpool.hxx> + +#include <cstddef> +#include <memory> +#include <unordered_map> +#include <utility> +#include <vector> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; + +#define TIMEOUT_FIRST 300 +#define TIMEOUT_UPDATING 20 + +struct SfxFoundCache_Impl +{ + sal_uInt16 nWhichId; // If available: Which-Id, else: nSlotId + const SfxSlot* pSlot; // Pointer to <Master-Slot> + SfxStateCache& rCache; // Pointer to StatusCache + + SfxFoundCache_Impl(sal_uInt16 nW, const SfxSlot *pS, SfxStateCache& rC) + : nWhichId(nW) + , pSlot(pS) + , rCache(rC) + {} +}; + +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->nMsgPos = 0; + pImpl->bAllMsgDirty = true; + pImpl->bContextChanged = false; + pImpl->bMsgDirty = true; + pImpl->bAllDirty = true; + pImpl->nCachedFunc1 = 0; + pImpl->nCachedFunc2 = 0; + pImpl->bCtrlReleased = false; + pImpl->bFirstRound = false; + pImpl->bInNextJob = false; + pImpl->bInUpdate = false; + pImpl->pSubBindings = nullptr; + pImpl->nOwnRegLevel = nRegLevel; + + // all caches are valid (no pending invalidate-job) + // create the list of caches + 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; + + ENTERREGISTRATIONS(); + + pImpl->aAutoTimer.Stop(); + DeleteControllers_Impl(); + + // Delete Caches + pImpl->pCaches.clear(); + + pImpl->mxWorkWin.reset(); +} + + +void SfxBindings::DeleteControllers_Impl() +{ + // in the first round delete Controllers + std::size_t nCount = pImpl->pCaches.size(); + std::size_t nCache; + for ( nCache = 0; nCache < nCount; ++nCache ) + { + // Remember were you are + SfxStateCache *pCache = pImpl->pCaches[nCache].get(); + sal_uInt16 nSlotId = pCache->GetId(); + + // Re-align, because the cache may have been reduced + std::size_t nNewCount = pImpl->pCaches.size(); + if ( nNewCount < nCount ) + { + nCache = GetSlotPos(nSlotId); + if ( nCache >= nNewCount || + nSlotId != pImpl->pCaches[nCache]->GetId() ) + --nCache; + nCount = nNewCount; + } + } + + // Delete all Caches + for ( nCache = pImpl->pCaches.size(); nCache > 0; --nCache ) + { + // Get Cache via css::sdbcx::Index + SfxStateCache *pCache = pImpl->pCaches[ nCache-1 ].get(); + + // unbind all controllers in the cache + SfxControllerItem *pNext; + for ( SfxControllerItem *pCtrl = pCache->GetItemLink(); + pCtrl; pCtrl = pNext ) + { + pNext = pCtrl->GetItemLink(); + pCtrl->UnBind(); + } + + if ( pCache->GetInternalController() ) + pCache->GetInternalController()->UnBind(); + + // Delete Cache + pImpl->pCaches.erase(pImpl->pCaches.begin() + nCache - 1); + } +} + + +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; + } + + pSet.reset(); + } + + if (!bUpdated) + { + SfxFoundCache_Impl aFoundCache(0, pRealSlot, rCache); + UpdateControllers_Impl( aFoundCache, nullptr, SfxItemState::DISABLED); + } +} + +void SfxBindings::InvalidateSlotsInMap_Impl() +{ + for (auto const& slot : pImpl->m_aInvalidateSlots) + Invalidate( slot.first ); + + pImpl->m_aInvalidateSlots.clear(); +} + + +void SfxBindings::AddSlotToInvalidateSlotsMap_Impl( sal_uInt16 nId ) +{ + pImpl->m_aInvalidateSlots[nId] = true; +} + + +void SfxBindings::Update +( + sal_uInt16 nId // the bound and up-to-date Slot-Id +) +{ + if ( pDispatcher ) + pDispatcher->Flush(); + + if ( pImpl->pSubBindings ) + pImpl->pSubBindings->Update( nId ); + + SfxStateCache* pCache = GetStateCache( nId ); + if ( !pCache ) + return; + + pImpl->bInUpdate = true; + if ( pImpl->bMsgDirty ) + { + UpdateSlotServer_Impl(); + pCache = GetStateCache( nId ); + } + + if (pCache) + { + bool bInternalUpdate = true; + if( pCache->GetDispatch().is() && pCache->GetItemLink() ) + { + pCache->SetCachedState(true); + bInternalUpdate = ( pCache->GetInternalController() != nullptr ); + } + + if ( bInternalUpdate ) + { + // Query Status + const SfxSlotServer* pMsgServer = pDispatcher ? pCache->GetSlotServer(*pDispatcher, pImpl->xProv) : nullptr; + if ( !pCache->IsControllerDirty() ) + { + pImpl->bInUpdate = false; + InvalidateSlotsInMap_Impl(); + return; + } + if (!pMsgServer) + { + pCache->SetState(SfxItemState::DISABLED, nullptr); + pImpl->bInUpdate = false; + InvalidateSlotsInMap_Impl(); + return; + } + + Update_Impl(*pCache); + } + + pImpl->bAllDirty = false; + } + + pImpl->bInUpdate = false; + InvalidateSlotsInMap_Impl(); +} + + +void SfxBindings::Update() +{ + if ( pImpl->pSubBindings ) + pImpl->pSubBindings->Update(); + + if ( !pDispatcher ) + return; + + if ( nRegLevel ) + return; + + pImpl->bInUpdate = true; + pDispatcher->Flush(); + pDispatcher->Update_Impl(); + while ( !NextJob_Impl(nullptr) ) + ; // loop + pImpl->bInUpdate = false; + InvalidateSlotsInMap_Impl(); +} + + +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; + } + + pImpl->bAllMsgDirty = pImpl->bAllMsgDirty || bWithMsg; + pImpl->bMsgDirty = pImpl->bMsgDirty || pImpl->bAllMsgDirty || bWithMsg; + pImpl->bAllDirty = true; + + for (std::unique_ptr<SfxStateCache>& pCache : pImpl->pCaches) + pCache->Invalidate(bWithMsg); + + pImpl->nMsgPos = 0; + if ( !nRegLevel ) + { + pImpl->aAutoTimer.Stop(); + pImpl->aAutoTimer.SetTimeout(TIMEOUT_FIRST); + pImpl->aAutoTimer.Start(); + } +} + + +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; + } + + // Find Level + sal_uInt16 nLevel = pDispatcher->GetShellLevel(rSh); + if ( nLevel == USHRT_MAX ) + return; + + for (std::unique_ptr<SfxStateCache>& pCache : pImpl->pCaches) + { + const SfxSlotServer *pMsgServer = + pCache->GetSlotServer(*pDispatcher, pImpl->xProv); + if ( pMsgServer && pMsgServer->GetShellLevel() == nLevel ) + pCache->Invalidate(false); + } + pImpl->nMsgPos = 0; + if ( !nRegLevel ) + { + pImpl->aAutoTimer.Stop(); + pImpl->aAutoTimer.SetTimeout(TIMEOUT_FIRST); + pImpl->aAutoTimer.Start(); + pImpl->bFirstRound = true; + } +} + + +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; + + SfxStateCache* pCache = GetStateCache(nId); + if ( pCache ) + { + pCache->Invalidate(false); + pImpl->nMsgPos = std::min(GetSlotPos(nId), pImpl->nMsgPos); + if ( !nRegLevel ) + { + pImpl->aAutoTimer.Stop(); + pImpl->aAutoTimer.SetTimeout(TIMEOUT_FIRST); + pImpl->aAutoTimer.Start(); + } + } +} + + +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); + + if ( !pDispatcher || pImpl->bAllDirty ) + return; + + pImpl->nMsgPos = std::min(GetSlotPos(nId), pImpl->nMsgPos); + if ( !nRegLevel ) + { + pImpl->aAutoTimer.Stop(); + pImpl->aAutoTimer.SetTimeout(TIMEOUT_FIRST); + pImpl->aAutoTimer.Start(); + } +} + + +std::size_t SfxBindings::GetSlotPos( sal_uInt16 nId, std::size_t nStartSearchAt ) +{ + // answer immediately if a function-seek comes repeated + if ( pImpl->nCachedFunc1 < pImpl->pCaches.size() && + pImpl->pCaches[pImpl->nCachedFunc1]->GetId() == nId ) + { + return pImpl->nCachedFunc1; + } + if ( pImpl->nCachedFunc2 < pImpl->pCaches.size() && + pImpl->pCaches[pImpl->nCachedFunc2]->GetId() == nId ) + { + // swap the caches + std::swap(pImpl->nCachedFunc1, pImpl->nCachedFunc2); + return pImpl->nCachedFunc1; + } + + // binary search, if not found, seek to target-position + if ( pImpl->pCaches.size() <= nStartSearchAt ) + { + return 0; + } + if ( pImpl->pCaches.size() == (nStartSearchAt+1) ) + { + return pImpl->pCaches[nStartSearchAt]->GetId() >= nId ? 0 : 1; + } + std::size_t nLow = nStartSearchAt; + std::size_t nMid = 0; + std::size_t nHigh = 0; + bool bFound = false; + nHigh = pImpl->pCaches.size() - 1; + while ( !bFound && nLow <= nHigh ) + { + nMid = (nLow + nHigh) >> 1; + DBG_ASSERT( nMid < pImpl->pCaches.size(), "bsearch is buggy" ); + int nDiff = static_cast<int>(nId) - static_cast<int>( (pImpl->pCaches[nMid])->GetId() ); + if ( nDiff < 0) + { if ( nMid == 0 ) + break; + nHigh = nMid - 1; + } + else if ( nDiff > 0 ) + { nLow = nMid + 1; + if ( nLow == 0 ) + break; + } + else + bFound = true; + } + std::size_t nPos = bFound ? nMid : nLow; + DBG_ASSERT( nPos <= pImpl->pCaches.size(), "" ); + DBG_ASSERT( nPos == pImpl->pCaches.size() || + nId <= pImpl->pCaches[nPos]->GetId(), "" ); + DBG_ASSERT( nPos == nStartSearchAt || + nId > pImpl->pCaches[nPos-1]->GetId(), "" ); + DBG_ASSERT( ( (nPos+1) >= pImpl->pCaches.size() ) || + nId < pImpl->pCaches[nPos+1]->GetId(), "" ); + pImpl->nCachedFunc2 = pImpl->nCachedFunc1; + pImpl->nCachedFunc1 = nPos; + return nPos; +} + +void SfxBindings::RegisterInternal_Impl( SfxControllerItem& rItem ) +{ + Register_Impl( rItem, true ); + +} + +void SfxBindings::Register( SfxControllerItem& rItem ) +{ + Register_Impl( rItem, false ); +} + +void SfxBindings::Register_Impl( SfxControllerItem& rItem, bool bInternal ) +{ +// DBG_ASSERT( nRegLevel > 0, "registration without EnterRegistrations" ); + DBG_ASSERT( !pImpl->bInNextJob, "SfxBindings::Register while status-updating" ); + + // insert new cache if it does not already exist + sal_uInt16 nId = rItem.GetId(); + std::size_t nPos = GetSlotPos(nId); + if ( nPos >= pImpl->pCaches.size() || + pImpl->pCaches[nPos]->GetId() != nId ) + { + pImpl->pCaches.insert( pImpl->pCaches.begin() + nPos, std::make_unique<SfxStateCache>(nId) ); + DBG_ASSERT( nPos == 0 || + pImpl->pCaches[nPos]->GetId() > + pImpl->pCaches[nPos-1]->GetId(), "" ); + DBG_ASSERT( (nPos == pImpl->pCaches.size()-1) || + pImpl->pCaches[nPos]->GetId() < + pImpl->pCaches[nPos+1]->GetId(), "" ); + pImpl->bMsgDirty = true; + } + + // enqueue the new binding + if ( bInternal ) + { + pImpl->pCaches[nPos]->SetInternalController( &rItem ); + } + else + { + SfxControllerItem *pOldItem = pImpl->pCaches[nPos]->ChangeItemLink(&rItem); + rItem.ChangeItemLink(pOldItem); + } +} + + +void SfxBindings::Release( SfxControllerItem& rItem ) +{ + DBG_ASSERT( !pImpl->bInNextJob, "SfxBindings::Release while status-updating" ); + ENTERREGISTRATIONS(); + + // 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; + } + } + + LEAVEREGISTRATIONS(); +} + + +const SfxPoolItem* SfxBindings::ExecuteSynchron( sal_uInt16 nId, const SfxPoolItem** ppItems ) +{ + if( !nId || !pDispatcher ) + return nullptr; + + return Execute_Impl( nId, ppItems, 0, SfxCallMode::SYNCHRON, nullptr ); +} + +bool SfxBindings::Execute( sal_uInt16 nId, const SfxPoolItem** ppItems, SfxCallMode nCallMode ) +{ + if( !nId || !pDispatcher ) + return false; + + const SfxPoolItem* pRet = Execute_Impl( nId, ppItems, 0, nCallMode, nullptr ); + return ( pRet != nullptr ); +} + +const SfxPoolItem* SfxBindings::Execute_Impl( sal_uInt16 nId, const SfxPoolItem** ppItems, sal_uInt16 nModi, SfxCallMode nCallMode, + const SfxPoolItem **ppInternalArgs, bool bGlobalOnly ) +{ + SfxStateCache *pCache = GetStateCache( nId ); + if ( !pCache ) + { + SfxBindings *pBind = pImpl->pSubBindings; + while ( pBind ) + { + if ( pBind->GetStateCache( nId ) ) + return pBind->Execute_Impl( nId, ppItems, nModi, nCallMode, ppInternalArgs, bGlobalOnly ); + pBind = pBind->pImpl->pSubBindings; + } + } + + SfxDispatcher &rDispatcher = *pDispatcher; + rDispatcher.Flush(); + + // 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() ) + { + DBG_ASSERT( !ppInternalArgs, "Internal args get lost when dispatched!" ); + + SfxItemPool &rPool = GetDispatcher()->GetFrame()->GetObjectShell()->GetPool(); + SfxRequest aReq( nId, nCallMode, rPool ); + aReq.SetModifier( nModi ); + if( ppItems ) + while( *ppItems ) + aReq.AppendItem( **ppItems++ ); + + // cache binds to an external dispatch provider + sal_Int16 eRet = pCache->Dispatch( aReq.GetArgs(), nCallMode == SfxCallMode::SYNCHRON ); + std::unique_ptr<SfxPoolItem> pPool; + if ( eRet == css::frame::DispatchResultState::DONTKNOW ) + pPool.reset( new SfxVoidItem( nId ) ); + else + pPool.reset( new SfxBoolItem( nId, eRet == css::frame::DispatchResultState::SUCCESS) ); + + auto pTemp = pPool.get(); + DeleteItemOnIdle( std::move(pPool) ); + return pTemp; + } + + // slot is handled internally by SfxDispatcher + if ( pImpl->bMsgDirty ) + UpdateSlotServer_Impl(); + + SfxShell *pShell=nullptr; + const SfxSlot *pSlot=nullptr; + + const SfxSlotServer* pServer = pCache->GetSlotServer( rDispatcher, pImpl->xProv ); + if ( !pServer ) + { + return nullptr; + } + else + { + pShell = rDispatcher.GetShell( pServer->GetShellLevel() ); + pSlot = pServer->GetSlot(); + } + + if ( bGlobalOnly ) + if ( dynamic_cast< const SfxModule *>( pShell ) == nullptr && dynamic_cast< const SfxApplication *>( pShell ) == nullptr && dynamic_cast< const SfxViewFrame *>( pShell ) == nullptr ) + return nullptr; + + SfxItemPool &rPool = pShell->GetPool(); + SfxRequest aReq( nId, nCallMode, rPool ); + aReq.SetModifier( nModi ); + if( ppItems ) + while( *ppItems ) + aReq.AppendItem( **ppItems++ ); + if ( ppInternalArgs ) + { + SfxAllItemSet aSet( rPool ); + for ( const SfxPoolItem **pArg = ppInternalArgs; *pArg; ++pArg ) + aSet.Put( **pArg ); + aReq.SetInternalArgs_Impl( aSet ); + } + + Execute_Impl( aReq, pSlot, pShell ); + + const SfxPoolItem* pRet = aReq.GetReturnValue(); + if ( !pRet ) + { + std::unique_ptr<SfxPoolItem> pVoid(new SfxVoidItem( nId )); + pRet = pVoid.get(); + DeleteItemOnIdle( std::move(pVoid) ); + } + + return pRet; +} + +void SfxBindings::Execute_Impl( SfxRequest& aReq, const SfxSlot* pSlot, SfxShell* pShell ) +{ + SfxItemPool &rPool = pShell->GetPool(); + + 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 ( SfxItemState::DEFAULT == eState && SfxItemPool::IsWhich(nWhich) ) + pOldItem = &aSet.Get(nWhich); + + if ( SfxItemState::SET == eState || + ( SfxItemState::DEFAULT == eState && + SfxItemPool::IsWhich(nWhich) && + pOldItem ) ) + { + if ( auto pOldBoolItem = dynamic_cast< const SfxBoolItem *>( pOldItem ) ) + { + // we can toggle Bools + bool bOldValue = pOldBoolItem->GetValue(); + std::unique_ptr<SfxBoolItem> pNewItem(static_cast<SfxBoolItem*>(pOldItem->Clone())); + pNewItem->SetValue( !bOldValue ); + aReq.AppendItem( *pNewItem ); + } + else if ( auto pOldEnumItem = dynamic_cast< const SfxEnumItemInterface *>( pOldItem ) ) + { + if (pOldEnumItem->HasBoolValue()) + { + // and Enums with Bool-Interface + std::unique_ptr<SfxEnumItemInterface> pNewItem( + static_cast<SfxEnumItemInterface*>(pOldEnumItem->Clone())); + pNewItem->SetBoolValue(!pOldEnumItem->GetBoolValue()); + aReq.AppendItem( *pNewItem ); + } + } + else { + OSL_FAIL( "Toggle only for Enums and Bools allowed" ); + } + } + else if ( SfxItemState::DONTCARE == eState ) + { + // Create one Status-Item for each Factory + std::unique_ptr<SfxPoolItem> pNewItem = pSlot->GetType()->CreateItem(); + DBG_ASSERT( pNewItem, "Toggle to slot without ItemFactory" ); + pNewItem->SetWhich( nWhich ); + + if ( auto pNewBoolItem = dynamic_cast<SfxBoolItem *>( pNewItem.get() ) ) + { + // we can toggle Bools + pNewBoolItem->SetValue( true ); + aReq.AppendItem( *pNewItem ); + } + else if ( auto pEnumItem = dynamic_cast<SfxEnumItemInterface *>( pNewItem.get() ) ) + { + if (pEnumItem->HasBoolValue()) + { + // and Enums with Bool-Interface + pEnumItem->SetBoolValue(true); + aReq.AppendItem( *pNewItem ); + } + } + else { + OSL_FAIL( "Toggle only for Enums and Bools allowed" ); + } + } + else { + OSL_FAIL( "suspicious Toggle-Slot" ); + } + } + + pDispatcher->Execute_( *pShell, *pSlot, aReq, aReq.GetCallMode() | SfxCallMode::RECORD ); + } + else + pDispatcher->Execute_( *pShell, *pSlot, aReq, aReq.GetCallMode() | SfxCallMode::RECORD ); +} + + +void SfxBindings::UpdateSlotServer_Impl() +{ + // synchronize + pDispatcher->Flush(); + + 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; + + if ( bInsert && bSameMethod ) + { + SfxFoundCache_Impl aFoundCache( + pSibling->GetWhich(rPool), + pSibling, *pSiblingCache); + + rFound.push_back( aFoundCache ); + } + + pSibling = pSibling->GetNextSlot(); + } + + // 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::DONTCARE == eState ) + { + // ambiguous + rCache.SetState( SfxItemState::DONTCARE, INVALID_POOL_ITEM ); + } + else if ( SfxItemState::DEFAULT == eState && + SfxItemPool::IsSlot(rFound.nWhichId) ) + { + // no Status or Default but without Pool + SfxVoidItem aVoid(0); + rCache.SetState( SfxItemState::UNKNOWN, &aVoid ); + } + else if ( SfxItemState::DISABLED == eState ) + rCache.SetState(SfxItemState::DISABLED, nullptr); + else + rCache.SetState(SfxItemState::DEFAULT, pItem); +} + +IMPL_LINK( SfxBindings, NextJob, Timer *, pTimer, void ) +{ + NextJob_Impl(pTimer); +} + +bool SfxBindings::NextJob_Impl(Timer const * pTimer) +{ + const unsigned MAX_INPUT_DELAY = 200; + + if ( Application::GetLastInputInterval() < MAX_INPUT_DELAY && pTimer ) + { + pImpl->aAutoTimer.SetTimeout(TIMEOUT_UPDATING); + return true; + } + + SfxApplication *pSfxApp = SfxGetpApp(); + + if( pDispatcher ) + pDispatcher->Update_Impl(); + + // modifying the SfxObjectInterface-stack without SfxBindings => nothing to do + SfxViewFrame* pFrame = pDispatcher ? pDispatcher->GetFrame() : nullptr; + if ( (pFrame && !pFrame->GetObjectShell()->AcceptStateUpdate()) || pSfxApp->IsDowning() || pImpl->pCaches.empty() ) + { + return true; + } + if ( !pDispatcher || !pDispatcher->IsFlushed() ) + { + return true; + } + + // if possible Update all server / happens in its own time slice + if ( pImpl->bMsgDirty ) + { + UpdateSlotServer_Impl(); + return false; + } + + pImpl->bAllDirty = false; + pImpl->aAutoTimer.SetTimeout(TIMEOUT_UPDATING); + + // 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(); + DBG_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; + } + + if ( bWasDirty && !bJobDone && bPreEmptive && (--nLoops == 0) ) + { + pImpl->bInNextJob = false; + return false; + } + } + } + + pImpl->nMsgPos = 0; + + pImpl->aAutoTimer.Stop(); + + // Update round is finished + pImpl->bInNextJob = false; + Broadcast(SfxHint(SfxHintId::UpdateDone)); + return true; +} + + +sal_uInt16 SfxBindings::EnterRegistrations(const char *pFile, int nLine) +{ + SAL_INFO( + "sfx.control", + std::setw(std::min(nRegLevel, sal_uInt16(8))) << ' ' << "this = " << this + << " Level = " << nRegLevel << " SfxBindings::EnterRegistrations " + << (pFile + ? SAL_STREAM("File: " << pFile << " Line: " << nLine) : "")); + + // When bindings are locked, also lock sub bindings. + if ( pImpl->pSubBindings ) + { + pImpl->pSubBindings->ENTERREGISTRATIONS(); + + // These EnterRegistrations are not "real" for the SubBindings + pImpl->pSubBindings->pImpl->nOwnRegLevel--; + + // Synchronize Bindings + pImpl->pSubBindings->nRegLevel = nRegLevel + pImpl->pSubBindings->pImpl->nOwnRegLevel + 1; + } + + pImpl->nOwnRegLevel++; + + // 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( const char *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); + } + } + } + + // restart background-processing + pImpl->nMsgPos = 0; + if ( !pFrame || !pFrame->GetObjectShell() ) + return; + if ( !pImpl->pCaches.empty() ) + { + pImpl->aAutoTimer.Stop(); + pImpl->aAutoTimer.SetTimeout(TIMEOUT_FIRST); + pImpl->aAutoTimer.Start(); + } + } + + SAL_INFO( + "sfx.control", + std::setw(std::min(nRegLevel, sal_uInt16(8))) << ' ' << "this = " << this + << " Level = " << nRegLevel << " SfxBindings::LeaveRegistrations " + << (pFile + ? SAL_STREAM("File: " << pFile << " Line: " << nLine) : "")); +} + + +void SfxBindings::SetDispatcher( SfxDispatcher *pDisp ) +{ + SfxDispatcher *pOldDispat = pDispatcher; + if ( pDisp == pDispatcher ) + return; + + if ( pOldDispat ) + { + SfxBindings* pBind = pOldDispat->GetBindings(); + while ( pBind ) + { + if ( pBind->pImpl->pSubBindings == this && pBind->pDispatcher != pDisp ) + pBind->SetSubBindings_Impl( nullptr ); + pBind = pBind->pImpl->pSubBindings; + } + } + + pDispatcher = pDisp; + + css::uno::Reference < css::frame::XDispatchProvider > xProv; + if ( pDisp ) + xProv.set( pDisp->GetFrame()->GetFrame().GetFrameInterface(), UNO_QUERY ); + + SetDispatchProvider_Impl( xProv ); + InvalidateAll( true ); + + if ( pDispatcher && !pOldDispat ) + { + if ( pImpl->pSubBindings && pImpl->pSubBindings->pDispatcher != pOldDispat ) + { + OSL_FAIL( "SubBindings already set before activating!" ); + pImpl->pSubBindings->ENTERREGISTRATIONS(); + } + LEAVEREGISTRATIONS(); + } + else if( !pDispatcher ) + { + ENTERREGISTRATIONS(); + if ( pImpl->pSubBindings && pImpl->pSubBindings->pDispatcher != pOldDispat ) + { + OSL_FAIL( "SubBindings still set even when deactivating!" ); + pImpl->pSubBindings->LEAVEREGISTRATIONS(); + } + } + + Broadcast( SfxHint( SfxHintId::DataChanged ) ); + + if ( !pDisp ) + return; + + SfxBindings* pBind = pDisp->GetBindings(); + while ( pBind && pBind != this ) + { + if ( !pBind->pImpl->pSubBindings ) + { + pBind->SetSubBindings_Impl( this ); + break; + } + + pBind = pBind->pImpl->pSubBindings; + } +} + + +void SfxBindings::ClearCache_Impl( sal_uInt16 nSlotId ) +{ + SfxStateCache* pCache = GetStateCache(nSlotId); + if (!pCache) + return; + pCache->ClearCache(); +} + + +void SfxBindings::StartUpdate_Impl( bool bComplete ) +{ + if ( pImpl->pSubBindings ) + pImpl->pSubBindings->StartUpdate_Impl( bComplete ); + + if ( !bComplete ) + // Update may be interrupted + NextJob_Impl(&pImpl->aAutoTimer); + else + // Update all slots in a row + NextJob_Impl(nullptr); +} + + +SfxItemState SfxBindings::QueryState( sal_uInt16 nSlot, std::unique_ptr<SfxPoolItem> &rpState ) +{ + css::uno::Reference< css::frame::XDispatch > xDisp; + SfxStateCache *pCache = GetStateCache( nSlot ); + if ( pCache ) + xDisp = pCache->GetDispatch(); + if ( xDisp.is() || !pCache ) + { + const SfxSlot* pSlot = SfxSlotPool::GetSlotPool( pDispatcher->GetFrame() ).GetSlot( nSlot ); + if ( !pSlot || !pSlot->pUnoName ) + return SfxItemState::DISABLED; + + css::util::URL aURL; + OUString aCmd( ".uno:" ); + aURL.Protocol = aCmd; + aURL.Path = OUString::createFromAscii(pSlot->GetUnoName()); + aCmd += aURL.Path; + aURL.Complete = aCmd; + aURL.Main = aCmd; + + if ( !xDisp.is() ) + xDisp = pImpl->xProv->queryDispatch( aURL, OUString(), 0 ); + + if ( xDisp.is() ) + { + if (!comphelper::getFromUnoTunnel<SfxOfficeDispatch>(xDisp)) + { + bool bDeleteCache = false; + if ( !pCache ) + { + pCache = new SfxStateCache( nSlot ); + pCache->GetSlotServer( *GetDispatcher_Impl(), pImpl->xProv ); + bDeleteCache = true; + } + + SfxItemState eState = SfxItemState::SET; + rtl::Reference<BindDispatch_Impl> xBind(new BindDispatch_Impl( xDisp, aURL, pCache, pSlot )); + xDisp->addStatusListener( xBind, aURL ); + if ( !xBind->GetStatus().IsEnabled ) + { + eState = SfxItemState::DISABLED; + } + else + { + css::uno::Any aAny = xBind->GetStatus().State; + const css::uno::Type& aType = aAny.getValueType(); + + if ( aType == cppu::UnoType<bool>::get() ) + { + bool bTemp = false; + aAny >>= bTemp ; + rpState.reset(new SfxBoolItem( nSlot, bTemp )); + } + else if ( aType == ::cppu::UnoType< ::cppu::UnoUnsignedShortType >::get() ) + { + sal_uInt16 nTemp = 0; + aAny >>= nTemp ; + rpState.reset(new SfxUInt16Item( nSlot, nTemp )); + } + else if ( aType == cppu::UnoType<sal_uInt32>::get() ) + { + sal_uInt32 nTemp = 0; + aAny >>= nTemp ; + rpState.reset(new SfxUInt32Item( nSlot, nTemp )); + } + else if ( aType == cppu::UnoType<OUString>::get() ) + { + OUString sTemp ; + aAny >>= sTemp ; + rpState.reset(new SfxStringItem( nSlot, sTemp )); + } + else + rpState.reset(new SfxVoidItem( nSlot )); + } + + xDisp->removeStatusListener( xBind, aURL ); + xBind->Release(); + xBind.clear(); + if ( bDeleteCache ) + { + delete pCache; + pCache = nullptr; + } + return eState; + } + } + } + + // 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. + const SfxPoolItem *pItem = nullptr; + SfxItemState eState = pDispatcher->QueryState( nSlot, pItem ); + if ( eState == SfxItemState::SET ) + { + DBG_ASSERT( pItem, "SfxItemState::SET but no item!" ); + if ( pItem ) + rpState.reset(pItem->Clone()); + } + else if ( eState == SfxItemState::DEFAULT && pItem ) + { + rpState.reset(pItem->Clone()); + } + + return eState; +} + +void SfxBindings::QueryControlState( sal_uInt16 nSlot, boost::property_tree::ptree& rState ) +{ + if ( SfxGetpApp()->IsDowning() ) + return; + + if ( pDispatcher ) + pDispatcher->Flush(); + + if ( pImpl->pSubBindings ) + pImpl->pSubBindings->QueryControlState( nSlot, rState ); + + SfxStateCache* pCache = GetStateCache( nSlot ); + if ( !pCache ) + return; + + if ( pImpl->bMsgDirty ) + { + UpdateSlotServer_Impl(); + pCache = GetStateCache( nSlot ); + } + + if (pCache && pCache->GetItemLink() ) + { + pCache->GetState(rState); + } +} + +sal_uInt16 SfxBindings::QuerySlotId( const util::URL& aURL ) +{ + if (!pImpl) + return 0; + + css::uno::Reference<css::frame::XDispatch> xDispatch = + pImpl->xProv->queryDispatch(aURL, OUString(), 0); + if (!xDispatch.is()) + return 0; + + css::uno::Reference<css::lang::XUnoTunnel> xTunnel(xDispatch, css::uno::UNO_QUERY); + if (!xTunnel.is()) + return 0; + + sal_Int64 nHandle = xTunnel->getSomething(SfxOfficeDispatch::getUnoTunnelId()); + if (!nHandle) + return 0; + + SfxOfficeDispatch* pDispatch = reinterpret_cast<SfxOfficeDispatch*>(sal::static_int_cast<sal_IntPtr>(nHandle)); + return pDispatch->GetId(); +} + +void SfxBindings::SetSubBindings_Impl( SfxBindings *pSub ) +{ + if ( pImpl->pSubBindings ) + { + pImpl->pSubBindings->SetDispatchProvider_Impl( css::uno::Reference< css::frame::XDispatchProvider > () ); + } + + pImpl->pSubBindings = pSub; + + if ( pSub ) + { + pImpl->pSubBindings->SetDispatchProvider_Impl( pImpl->xProv ); + } +} + +SfxBindings* SfxBindings::GetSubBindings_Impl() const +{ + return pImpl->pSubBindings; +} + +void SfxBindings::SetWorkWindow_Impl( std::unique_ptr<SfxWorkWindow> xWork ) +{ + pImpl->mxWorkWin = std::move(xWork); +} + +SfxWorkWindow* SfxBindings::GetWorkWindow_Impl() const +{ + return pImpl->mxWorkWin.get(); +} + +bool SfxBindings::IsInUpdate() const +{ + bool bInUpdate = pImpl->bInUpdate; + if ( !bInUpdate && pImpl->pSubBindings ) + bInUpdate = pImpl->pSubBindings->IsInUpdate(); + return bInUpdate; +} + +void SfxBindings::SetVisibleState( sal_uInt16 nId, bool bShow ) +{ + SfxStateCache *pCache = GetStateCache( nId ); + if ( pCache ) + pCache->SetVisibleState( bShow ); +} + +void SfxBindings::SetActiveFrame( const css::uno::Reference< css::frame::XFrame > & rFrame ) +{ + if ( rFrame.is() || !pDispatcher ) + SetDispatchProvider_Impl( css::uno::Reference< css::frame::XDispatchProvider > ( rFrame, css::uno::UNO_QUERY ) ); + else + SetDispatchProvider_Impl( css::uno::Reference< css::frame::XDispatchProvider > ( + pDispatcher->GetFrame()->GetFrame().GetFrameInterface(), css::uno::UNO_QUERY ) ); +} + +css::uno::Reference< css::frame::XFrame > SfxBindings::GetActiveFrame() const +{ + const css::uno::Reference< css::frame::XFrame > xFrame( pImpl->xProv, css::uno::UNO_QUERY ); + if ( xFrame.is() || !pDispatcher ) + return xFrame; + else + return pDispatcher->GetFrame()->GetFrame().GetFrameInterface(); +} + +void SfxBindings::SetDispatchProvider_Impl( const css::uno::Reference< css::frame::XDispatchProvider > & rProv ) +{ + bool bInvalidate = ( rProv != pImpl->xProv ); + if ( bInvalidate ) + { + pImpl->xProv = rProv; + InvalidateAll( true ); + } + + if ( pImpl->pSubBindings ) + pImpl->pSubBindings->SetDispatchProvider_Impl( pImpl->xProv ); +} + +const css::uno::Reference< css::frame::XDispatchRecorder >& SfxBindings::GetRecorder() const +{ + return pImpl->xRecorder; +} + +void SfxBindings::SetRecorder_Impl( css::uno::Reference< css::frame::XDispatchRecorder > const & rRecorder ) +{ + pImpl->xRecorder = rRecorder; +} + +void SfxBindings::ContextChanged_Impl() +{ + if ( !pImpl->bInUpdate && ( !pImpl->bContextChanged || !pImpl->bAllMsgDirty ) ) + { + InvalidateAll( true ); + } +} + +uno::Reference < frame::XDispatch > SfxBindings::GetDispatch( const SfxSlot* pSlot, const util::URL& aURL, bool bMasterCommand ) +{ + uno::Reference < frame::XDispatch > xRet; + SfxStateCache* pCache = GetStateCache( pSlot->nSlotId ); + if ( pCache && !bMasterCommand ) + xRet = pCache->GetInternalDispatch(); + if ( !xRet.is() ) + { + // dispatches for slaves are unbound, they don't have a state + SfxOfficeDispatch* pDispatch = bMasterCommand ? + new SfxOfficeDispatch( pDispatcher, pSlot, aURL ) : + new SfxOfficeDispatch( *this, pDispatcher, pSlot, aURL ); + + pDispatch->SetMasterUnoCommand( bMasterCommand ); + xRet.set( pDispatch ); + if ( !pCache ) + pCache = GetStateCache( pSlot->nSlotId ); + + DBG_ASSERT( pCache, "No cache for OfficeDispatch!" ); + if ( pCache && !bMasterCommand ) + pCache->SetInternalDispatch( xRet ); + } + + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |