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 | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.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 '')
33 files changed, 15182 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: */ diff --git a/sfx2/source/control/charmapcontrol.cxx b/sfx2/source/control/charmapcontrol.cxx new file mode 100644 index 000000000..a79da745b --- /dev/null +++ b/sfx2/source/control/charmapcontrol.cxx @@ -0,0 +1,222 @@ +/* -*- 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 <comphelper/dispatchcommand.hxx> +#include <officecfg/Office/Common.hxx> +#include <charmapcontrol.hxx> +#include <charmappopup.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/strings.hrc> +#include <sfx2/sfxresid.hxx> + +using namespace css; + +SfxCharmapCtrl::SfxCharmapCtrl(CharmapPopup* pControl, weld::Widget* pParent) + : WeldToolbarPopup(pControl->getFrameInterface(), pParent, "sfx/ui/charmapcontrol.ui", "charmapctrl") + , m_xControl(pControl) + , m_xVirDev(VclPtr<VirtualDevice>::Create()) + , m_aRecentCharView{SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev)} + , m_aFavCharView{SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev)} + , m_xRecentLabel(m_xBuilder->weld_label("label2")) + , m_xDlgBtn(m_xBuilder->weld_button("specialchardlg")) + , m_xRecentCharView{std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar1", m_aRecentCharView[0]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar2", m_aRecentCharView[1]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar3", m_aRecentCharView[2]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar4", m_aRecentCharView[3]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar5", m_aRecentCharView[4]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar6", m_aRecentCharView[5]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar7", m_aRecentCharView[6]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar8", m_aRecentCharView[7]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar9", m_aRecentCharView[8]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar10", m_aRecentCharView[9]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar11", m_aRecentCharView[10]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar12", m_aRecentCharView[11]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar13", m_aRecentCharView[12]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar14", m_aRecentCharView[13]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar15", m_aRecentCharView[14]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar16", m_aRecentCharView[15])} + , m_xFavCharView{std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar1", m_aFavCharView[0]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar2", m_aFavCharView[1]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar3", m_aFavCharView[2]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar4", m_aFavCharView[3]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar5", m_aFavCharView[4]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar6", m_aFavCharView[5]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar7", m_aFavCharView[6]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar8", m_aFavCharView[7]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar9", m_aFavCharView[8]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar10", m_aFavCharView[9]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar11", m_aFavCharView[10]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar12", m_aFavCharView[11]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar13", m_aFavCharView[12]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar14", m_aFavCharView[13]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar15", m_aFavCharView[14]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar16", m_aFavCharView[15])} +{ + for(int i = 0; i < 16; i++) + { + m_aRecentCharView[i].setMouseClickHdl(LINK(this,SfxCharmapCtrl, CharClickHdl)); + m_aFavCharView[i].setMouseClickHdl(LINK(this,SfxCharmapCtrl, CharClickHdl)); + } + + m_xDlgBtn->connect_clicked(LINK(this, SfxCharmapCtrl, OpenDlgHdl)); + + getRecentCharacterList(); + updateRecentCharControl(); + getFavCharacterList(); + updateFavCharControl(); +} + +SfxCharmapCtrl::~SfxCharmapCtrl() +{ +} + +void SfxCharmapCtrl::getFavCharacterList() +{ + //retrieve recent character list + const css::uno::Sequence< OUString > rFavCharList( officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterList::get() ); + m_aFavCharList.insert( m_aFavCharList.end(), rFavCharList.begin(), rFavCharList.end() ); + + //retrieve recent character font list + const css::uno::Sequence< OUString > rFavCharFontList( officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterFontList::get() ); + m_aFavCharFontList.insert( m_aFavCharFontList.end(), rFavCharFontList.begin(), rFavCharFontList.end() ); + + // tdf#135997: make sure that the two lists are same length + const auto nCommonLength = std::min(m_aFavCharList.size(), m_aFavCharFontList.size()); + m_aFavCharList.resize(nCommonLength); + m_aFavCharFontList.resize(nCommonLength); +} + +void SfxCharmapCtrl::updateFavCharControl() +{ + assert(m_aFavCharList.size() == m_aFavCharFontList.size()); + + int i = 0; + for ( std::deque< OUString >::iterator it = m_aFavCharList.begin(), it2 = m_aFavCharFontList.begin(); + it != m_aFavCharList.end() && it2 != m_aFavCharFontList.end(); + ++it, ++it2, i++) + { + m_aFavCharView[i].SetText(*it); + vcl::Font rFont = m_aFavCharView[i].GetFont(); + rFont.SetFamilyName( *it2 ); + m_aFavCharView[i].SetFont(rFont); + m_aFavCharView[i].Show(); + } + + for(; i < 16 ; i++) + { + m_aFavCharView[i].SetText(OUString()); + m_aFavCharView[i].Hide(); + } +} + +void SfxCharmapCtrl::getRecentCharacterList() +{ + //retrieve recent character list + const css::uno::Sequence< OUString > rRecentCharList( officecfg::Office::Common::RecentCharacters::RecentCharacterList::get() ); + m_aRecentCharList.insert( m_aRecentCharList.end(), rRecentCharList.begin(), rRecentCharList.end() ); + + //retrieve recent character font list + const css::uno::Sequence< OUString > rRecentCharFontList( officecfg::Office::Common::RecentCharacters::RecentCharacterFontList::get() ); + m_aRecentCharFontList.insert( m_aRecentCharFontList.end(), rRecentCharFontList.begin(), rRecentCharFontList.end() ); + + // tdf#135997: make sure that the two lists are same length + const auto nCommonLength = std::min(m_aRecentCharList.size(), m_aRecentCharFontList.size()); + m_aRecentCharList.resize(nCommonLength); + m_aRecentCharFontList.resize(nCommonLength); +} + +void SfxCharmapCtrl::updateRecentCharControl() +{ + assert(m_aRecentCharList.size() == m_aRecentCharFontList.size()); + int i = 0; + for ( std::deque< OUString >::iterator it = m_aRecentCharList.begin(), it2 = m_aRecentCharFontList.begin(); + it != m_aRecentCharList.end() && it2 != m_aRecentCharFontList.end(); + ++it, ++it2, i++) + { + m_aRecentCharView[i].SetText(*it); + vcl::Font rFont = m_aRecentCharView[i].GetFont(); + rFont.SetFamilyName( *it2 ); + m_aRecentCharView[i].SetFont(rFont); + m_aRecentCharView[i].Show(); + } + + for(; i < 16 ; i++) + { + m_aRecentCharView[i].SetText(OUString()); + m_aRecentCharView[i].Hide(); + } + + //checking if the characters are recently used or no + m_xRecentLabel->set_label(m_aRecentCharList.size() > 0 ? SfxResId(STR_RECENT) : SfxResId(STR_NORECENT)); +} + +IMPL_LINK(SfxCharmapCtrl, CharClickHdl, SvxCharView*, pView, void) +{ + m_xControl->EndPopupMode(); + + pView->InsertCharToDoc(); +} + +IMPL_LINK_NOARG(SfxCharmapCtrl, OpenDlgHdl, weld::Button&, void) +{ + m_xControl->EndPopupMode(); + + if (SfxViewFrame* pViewFrm = SfxViewFrame::Current()) + { + uno::Reference<frame::XFrame> xFrame = pViewFrm->GetFrame().GetFrameInterface(); + comphelper::dispatchCommand(".uno:InsertSymbol", xFrame, {}); + } +} + +void SfxCharmapCtrl::GrabFocus() +{ + m_aFavCharView[0].GrabFocus(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/charwin.cxx b/sfx2/source/control/charwin.cxx new file mode 100644 index 000000000..567f365e6 --- /dev/null +++ b/sfx2/source/control/charwin.cxx @@ -0,0 +1,263 @@ +/* -*- 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 <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <sfx2/charwin.hxx> +#include <comphelper/dispatchcommand.hxx> +#include <comphelper/propertyvalue.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +using namespace com::sun::star; + +SvxCharView::SvxCharView(const VclPtr<VirtualDevice>& rVirDev) + : mxVirDev(rVirDev) + , mnY(0) + , maPosition(0, 0) + , maHasInsert(true) +{ +} + +void SvxCharView::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + vcl::Font aFont = rStyleSettings.GetLabelFont(); + const Size aFontSize = aFont.GetFontSize(); + aFont.SetFontSize(Size(aFontSize.Width() * 2.5, aFontSize.Height() * 2.5)); + mxVirDev->Push(PUSH_ALLFONT); + mxVirDev->SetFont(aFont); + pDrawingArea->set_size_request(mxVirDev->approximate_digit_width() * 2, + mxVirDev->GetTextHeight()); + mxVirDev->Pop(); +} + +void SvxCharView::GetFocus() { Invalidate(); } + +void SvxCharView::LoseFocus() { Invalidate(); } + +bool SvxCharView::MouseButtonDown(const MouseEvent& rMEvt) +{ + if (rMEvt.IsLeft()) + { + if (!(rMEvt.GetClicks() % 2) && maHasInsert) + { + InsertCharToDoc(); + } + + maMouseClickHdl.Call(this); + } + + if (rMEvt.IsRight()) + { + Point aPosition(rMEvt.GetPosPixel()); + maPosition = aPosition; + GrabFocus(); + Invalidate(); + createContextMenu(); + } + + return true; +} + +bool SvxCharView::KeyInput(const KeyEvent& rKEvt) +{ + bool bRet = false; + vcl::KeyCode aCode = rKEvt.GetKeyCode(); + switch (aCode.GetCode()) + { + case KEY_SPACE: + case KEY_RETURN: + InsertCharToDoc(); + bRet = true; + break; + } + return bRet; +} + +void SvxCharView::InsertCharToDoc() +{ + if (GetText().isEmpty()) + return; + + uno::Sequence<beans::PropertyValue> aArgs{ comphelper::makePropertyValue("Symbols", GetText()), + comphelper::makePropertyValue( + "FontName", maFont.GetFamilyName()) }; + + comphelper::dispatchCommand(".uno:InsertSymbol", aArgs); +} + +void SvxCharView::createContextMenu() +{ + weld::DrawingArea* pDrawingArea = GetDrawingArea(); + std::unique_ptr<weld::Builder> xBuilder( + Application::CreateBuilder(pDrawingArea, "sfx/ui/charviewmenu.ui")); + std::unique_ptr<weld::Menu> xItemMenu(xBuilder->weld_menu("charviewmenu")); + ContextMenuSelect( + xItemMenu->popup_at_rect(pDrawingArea, tools::Rectangle(maPosition, Size(1, 1)))); + Invalidate(); +} + +void SvxCharView::ContextMenuSelect(std::string_view rMenuId) +{ + if (rMenuId == "clearchar") + maClearClickHdl.Call(this); + else if (rMenuId == "clearallchar") + maClearAllClickHdl.Call(this); +} + +void SvxCharView::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + rRenderContext.SetFont(maFont); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const Color aWindowTextColor(rStyleSettings.GetFieldTextColor()); + Color aHighlightColor(rStyleSettings.GetHighlightColor()); + Color aHighlightTextColor(rStyleSettings.GetHighlightTextColor()); + Color aFillColor(rStyleSettings.GetWindowColor()); + Color aTextColor(rStyleSettings.GetWindowTextColor()); + Color aShadowColor(rStyleSettings.GetShadowColor()); + + const OUString aText = GetText(); + + Size aSize(GetOutputSizePixel()); + tools::Long nAvailWidth = aSize.Width(); + tools::Long nWinHeight = aSize.Height(); + + bool bGotBoundary = true; + bool bShrankFont = false; + vcl::Font aOrigFont(rRenderContext.GetFont()); + Size aFontSize(aOrigFont.GetFontSize()); + ::tools::Rectangle aBoundRect; + + for (tools::Long nFontHeight = aFontSize.Height(); nFontHeight > 0; nFontHeight -= 1) + { + if (!rRenderContext.GetTextBoundRect(aBoundRect, aText) || aBoundRect.IsEmpty()) + { + bGotBoundary = false; + break; + } + + //only shrink in the single glyph large view mode + tools::Long nTextWidth = aBoundRect.GetWidth(); + if (nAvailWidth > nTextWidth) + break; + vcl::Font aFont(aOrigFont); + aFontSize.setHeight(nFontHeight); + aFont.SetFontSize(aFontSize); + rRenderContext.SetFont(aFont); + mnY = (nWinHeight - rRenderContext.GetTextHeight()) / 2; + bShrankFont = true; + } + + Point aPoint(2, mnY); + + if (!bGotBoundary) + aPoint.setX((aSize.Width() - rRenderContext.GetTextWidth(aText)) / 2); + else + { + // adjust position + aBoundRect += aPoint; + + // vertical adjustment + int nYLDelta = aBoundRect.Top(); + int nYHDelta = aSize.Height() - aBoundRect.Bottom(); + if (nYLDelta <= 0) + aPoint.AdjustY(-(nYLDelta - 1)); + else if (nYHDelta <= 0) + aPoint.AdjustY(nYHDelta - 1); + + // centrally align glyph + aPoint.setX(-aBoundRect.Left() + (aSize.Width() - aBoundRect.GetWidth()) / 2); + } + + // tdf#111924 - don't lose focus on context menu + if (HasFocus() || HasChildFocus()) + { + rRenderContext.SetFillColor(aHighlightColor); + rRenderContext.DrawRect(tools::Rectangle(Point(0, 0), aSize)); + + rRenderContext.SetTextColor(aHighlightTextColor); + rRenderContext.DrawText(aPoint, aText); + } + else + { + rRenderContext.SetFillColor(aFillColor); + rRenderContext.SetLineColor(aShadowColor); + rRenderContext.DrawRect(tools::Rectangle(Point(0, 0), aSize)); + + rRenderContext.SetTextColor(aWindowTextColor); + rRenderContext.DrawText(aPoint, aText); + } + rRenderContext.SetFillColor(aFillColor); + rRenderContext.SetTextColor(aTextColor); + + if (bShrankFont) + rRenderContext.SetFont(aOrigFont); +} + +void SvxCharView::setMouseClickHdl(const Link<SvxCharView*, void>& rLink) +{ + maMouseClickHdl = rLink; +} + +void SvxCharView::setClearClickHdl(const Link<SvxCharView*, void>& rLink) +{ + maClearClickHdl = rLink; +} + +void SvxCharView::setClearAllClickHdl(const Link<SvxCharView*, void>& rLink) +{ + maClearAllClickHdl = rLink; +} + +void SvxCharView::SetFont(const vcl::Font& rFont) +{ + tools::Long nWinHeight = GetOutputSizePixel().Height(); + maFont = rFont; + maFont.SetWeight(WEIGHT_NORMAL); + maFont.SetAlignment(ALIGN_TOP); + maFont.SetFontSize(mxVirDev->PixelToLogic(Size(0, nWinHeight / 2))); + maFont.SetTransparent(true); + + mxVirDev->Push(PUSH_ALLFONT); + mxVirDev->SetFont(maFont); + mnY = (nWinHeight - mxVirDev->GetTextHeight()) / 2; + mxVirDev->Pop(); + + Invalidate(); +} + +void SvxCharView::Resize() +{ + SetFont(GetFont()); //force recalculation of size +} + +void SvxCharView::SetText(const OUString& rText) +{ + m_sText = rText; + Invalidate(); +} + +void SvxCharView::SetHasInsert(bool bInsert) { maHasInsert = bInsert; } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sfx2/source/control/ctrlitem.cxx b/sfx2/source/control/ctrlitem.cxx new file mode 100644 index 000000000..28edfec66 --- /dev/null +++ b/sfx2/source/control/ctrlitem.cxx @@ -0,0 +1,344 @@ +/* -*- 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/log.hxx> +#include <svl/itempool.hxx> + +#include <sfx2/ctrlitem.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <statcach.hxx> +#include <sfx2/viewfrm.hxx> + +// returns the next registered SfxControllerItem with the same id + +SfxControllerItem* SfxControllerItem::GetItemLink() +{ + return pNext == this ? nullptr : pNext; +} + + +// returns sal_True if this binding is really bound to a function + +bool SfxControllerItem::IsBound() const +{ + return pNext != this; +} + + +// registers with the id at the bindings + +void SfxControllerItem::Bind( sal_uInt16 nNewId, SfxBindings *pBindinx ) +{ + DBG_ASSERT(pBindings || pBindinx, "No Bindings"); + + if ( IsBound() ) { + DBG_ASSERT(pBindings, "No Bindings"); + pBindings->Release(*this); + } + + nId = nNewId; + pNext = nullptr; + + if (pBindinx) + pBindings = pBindinx; + pBindings->Register(*this); +} + +void SfxControllerItem::BindInternal_Impl( sal_uInt16 nNewId, SfxBindings *pBindinx ) +{ + DBG_ASSERT(pBindings || pBindinx, "No Bindings"); + + if ( IsBound() ) { + DBG_ASSERT(pBindings, "No Bindings"); + pBindings->Release(*this); + } + + nId = nNewId; + pNext = nullptr; + + if (pBindinx) + pBindings = pBindinx; + pBindings->RegisterInternal_Impl(*this); +} + + +void SfxControllerItem::UnBind() + +/* [Description] + + Unbinds the connection of this SfxControllerItems with the SfxBindings + instance with which it to time is bound. From this time on it does not + receive any status notifications (<SfxControllerItem::StateChented()>) + anymore. + + [Cross-reference] + + <SfxControllerItem::ReBind()> + <SfxControllerItem::ClearCache()> +*/ +{ + DBG_ASSERT(pBindings, "No Bindings"); + DBG_ASSERT( IsBound(), "unbindings unbound SfxControllerItem" ); + + pBindings->Release(*this); + pNext = this; +} + + +void SfxControllerItem::ReBind() + +/* [Description] + + Binds this SfxControllerItem with the SfxBindings instance again, + with which it was last bound. From this time on it does receive status + notifications (<SfxControllerItem::StateChented()>) again. + + [Cross-reference] + + <SfxControllerItem::UnBind()> + <SfxControllerItem::ClearCache()> +*/ + +{ + DBG_ASSERT(pBindings, "No Bindings"); + DBG_ASSERT( !IsBound(), "bindings rebound SfxControllerItem" ); + + pBindings->Register(*this); +} + + +void SfxControllerItem::ClearCache() + +/* [Description] + + Clears the cache status for this SfxControllerItem. That is by the next + status update is the <SfxPoolItem> sent in any case, even if the same was + sent before. This is needed if a controller can be switched on and note + that status themselves. + + [Example] + + The combined controller for adjusting the surface type and the concrete + expression (blue color, or hatching X) can be changed in type, but is then + notified of the next selection again, even if it the same data. + + [Cross-reference] + + <SfxControllerItem::UnBind()> + <SfxControllerItem::ReBind()> +*/ + + +{ + DBG_ASSERT(pBindings, "No Bindings"); + + pBindings->ClearCache_Impl( GetId() ); +} + +// replaces the successor in the list of bindings of the same id +SfxControllerItem* SfxControllerItem::ChangeItemLink( SfxControllerItem* pNewLink ) +{ + SfxControllerItem* pOldLink = pNext; + pNext = pNewLink; + return pOldLink == this ? nullptr : pOldLink; +} + + +// changes the id of unbound functions (e.g. for sub-menu-ids) +void SfxControllerItem::SetId( sal_uInt16 nItemId ) +{ + DBG_ASSERT( !IsBound(), "changing id of bound binding" ); + nId = nItemId; +} + +// creates an atomic item for a controller without registration. +SfxControllerItem::SfxControllerItem() + : pNext(this) + , pBindings(nullptr) + , eFallbackCoreMetric(MapUnit::Map100thMM) + , nId(0) +{ +} + +// creates a representation of the function nId and registers it +SfxControllerItem::SfxControllerItem(sal_uInt16 nID, SfxBindings &rBindings) + : pNext(this) + , pBindings(&rBindings) + , eFallbackCoreMetric(MapUnit::Map100thMM) + , nId(nID) +{ + Bind(nId, &rBindings); +} + +// unregisters the item in the bindings +SfxControllerItem::~SfxControllerItem() +{ + dispose(); +} + +void SfxControllerItem::dispose() +{ + if ( IsBound() ) + UnBind(); +} + +void SfxControllerItem::StateChangedAtToolBoxControl +( + sal_uInt16, // <SID> of the triggering slot + SfxItemState, // <SfxItemState> of 'pState' + const SfxPoolItem* // Slot-Status, NULL or IsInvalidItem() +) + +/* [Description] + + This virtual method is called by the SFx to inform the <SfxControllerItem>s + is about that state of the slots 'NSID' has changed. The new value and the + value determined by this status is given as 'pState' or 'eState'. + + The status of a slot may change, for example when the MDI window is + switched or when the slot was invalidated explicitly with + <SfxBindings::Invalidate()>. + + Beware! The method is not called when the slot is invalid, however + has again assumed the same value. + + This base class need not be called, further interim steps however + (eg <SfxToolboxControl> ) should be called. +*/ + +{ +} + +void SfxControllerItem::GetControlState +( + sal_uInt16, + boost::property_tree::ptree& +) +{ +} + +void SfxStatusForwarder::StateChangedAtToolBoxControl +( + sal_uInt16 nSID, // <SID> of the triggering slot + SfxItemState eState, // <SfxItemState> of 'pState' + const SfxPoolItem* pState // Slot-Status, NULL or IsInvalidItem() +) + +{ + pMaster->StateChangedAtToolBoxControl( nSID, eState, pState ); +} + + +SfxStatusForwarder::SfxStatusForwarder( + sal_uInt16 nSlotId, + SfxControllerItem& rMaster ): + SfxControllerItem( nSlotId, rMaster.GetBindings() ), + pMaster( &rMaster ) +{ +} + + +SfxItemState SfxControllerItem::GetItemState +( + const SfxPoolItem* pState /* Pointer to <SfxPoolItem>, which + Status should be queried. */ +) + +/* [Description] + + Static method to determine the status of the SfxPoolItem-Pointers, to be + used in the method <SfxControllerItem::StateChanged(const SfxPoolItem*)> + + [Return value] + + SfxItemState SfxItemState::UNKNOWN + Enabled, but no further status information available. + Typical for <Slot>s, which anyway are sometimes + disabled, but otherwise do not change their appearance. + + SfxItemState::DISABLED + Disabled and no further status information available. + All other values that may appear should be reset to + default. + + SfxItemState::DONTCARE + Enabled but there were only ambiguous values available + (i.e. non that can be queried). + + SfxItemState::DEFAULT + Enabled and with available values, which are queried + by 'pState'. The Type is thus clearly defined in the + entire Program and specified through the Slot. +*/ + +{ + return !pState + ? SfxItemState::DISABLED + : IsInvalidItem(pState) + ? SfxItemState::DONTCARE + : pState->IsVoidItem() && !pState->Which() + ? SfxItemState::UNKNOWN + : SfxItemState::DEFAULT; +} + + +MapUnit SfxControllerItem::GetCoreMetric() const + +/* [Description] + + Gets the measurement unit from the competent pool, in which the Status + item exist. +*/ + +{ + SfxStateCache *pCache = pBindings->GetStateCache( nId ); + SfxDispatcher *pDispat = pBindings->GetDispatcher_Impl(); + + if ( !pDispat ) + { + SfxViewFrame* pViewFrame = SfxViewFrame::Current(); + if ( !pViewFrame ) + SfxViewFrame::GetFirst(); + if ( pViewFrame ) + pDispat = pViewFrame->GetDispatcher(); + } + + if ( pDispat && pCache ) + { + const SfxSlotServer *pServer = pCache->GetSlotServer( *pDispat ); + if ( pServer ) + { + SfxShell *pSh = pDispat->GetShell( pServer->GetShellLevel() ); + SfxItemPool &rPool = pSh->GetPool(); + sal_uInt16 nWhich = rPool.GetWhich( nId ); + + // invalidate slot and its message|slot server as 'global' information + // about the validated message|slot server is not made available + pCache->Invalidate( true ); + + return rPool.GetMetric( nWhich ); + } + } + + SAL_INFO( "sfx.control", "W1: Can not find ItemPool!" ); + return eFallbackCoreMetric; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/dispatch.cxx b/sfx2/source/control/dispatch.cxx new file mode 100644 index 000000000..17edf97cc --- /dev/null +++ b/sfx2/source/control/dispatch.cxx @@ -0,0 +1,2076 @@ +/* -*- 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 <config_feature_desktop.h> + +#include <algorithm> +#include <cstddef> +#include <deque> +#include <vector> + +#include <stdlib.h> + +#include <boost/property_tree/json_parser.hpp> + +#include <com/sun/star/awt/PopupMenuDirection.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XDispatchRecorderSupplier.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/frame/XPopupMenuController.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ui/ContextMenuExecuteEvent.hpp> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <sfx2/app.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/childwin.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfile.hxx> +#include <hintpost.hxx> +#include <sfx2/ipclient.hxx> +#include <sfx2/module.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/msgpool.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <sfx2/viewsh.hxx> +#include <svl/eitem.hxx> +#include <svl/itemiter.hxx> +#include <svl/itempool.hxx> +#include <toolkit/awt/vclxmenu.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/debug.hxx> +#include <vcl/idle.hxx> +#include <vcl/menu.hxx> + +#include <sfxtypes.hxx> +#include <slotserv.hxx> +#include <workwin.hxx> + +typedef std::vector<SfxShell*> SfxShellStack_Impl; + +namespace { + +struct SfxToDo_Impl +{ + SfxShell* pCluster; + bool bPush; + bool bDelete; + bool bDeleted; + bool bUntil; + + SfxToDo_Impl( bool bOpPush, bool bOpDelete, bool bOpUntil, SfxShell& rCluster ) + : pCluster(&rCluster) + , bPush(bOpPush) + , bDelete(bOpDelete) + , bDeleted(false) + , bUntil(bOpUntil) + {} +}; + +struct SfxObjectBars_Impl +{ + ToolbarId eId; // ConfigId of the Toolbox + sal_uInt16 nPos; + SfxVisibilityFlags nFlags; // special visibility flags + + SfxObjectBars_Impl() : eId(ToolbarId::None), nPos(0), nFlags(SfxVisibilityFlags::Invisible) {} +}; + +} + +struct SfxDispatcher_Impl +{ + //When the dispatched is locked, SfxRequests accumulate in aReqArr for + //later dispatch when unlocked via Post + + //The pointers are typically deleted in Post, only if we never get around + //to posting them do we delete the unposted requests. + std::vector<std::unique_ptr<SfxRequest>> + aReqArr; + SfxShellStack_Impl aStack; // active functionality + Idle aIdle { "sfx::SfxDispatcher_Impl aIdle" }; // for Flush + std::deque<SfxToDo_Impl> aToDoStack; // not processed Push/Pop + SfxViewFrame* pFrame; // NULL or associated Frame + tools::SvRef<SfxHintPoster> + xPoster; // Execute asynchronous + bool bFlushing; // sal_True during Flush //? + bool bUpdated; // Update_Impl has run + bool bLocked; // No Execute + bool bInvalidateOnUnlock; // because someone asked + bool bActive; // not to be confused with set! + bool* pInCallAliveFlag; // view the Destructor Stack + SfxObjectBars_Impl aObjBars[SFX_OBJECTBAR_MAX]; + SfxObjectBars_Impl aFixedObjBars[SFX_OBJECTBAR_MAX]; + std::vector<sal_uInt32> aChildWins; + bool bNoUI; // UI only from Parent Dispatcher + bool bReadOnly; // Document is ReadOnly + bool bQuiet; // Only use parent dispatcher + + SfxSlotFilterState nFilterEnabling; // 1==filter enabled slots, + // 2==ReadOnlyDoc overturned + o3tl::span<sal_uInt16 const> + pFilterSIDs; // sorted Array of SIDs + SfxDisableFlags nDisableFlags; + bool bFlushed; + std::deque< std::deque<SfxToDo_Impl> > aToDoCopyStack; +}; + +/** This method checks if the stack of the SfxDispatchers is flushed, or if + push- or pop- commands are pending. +*/ +bool SfxDispatcher::IsFlushed() const +{ + return xImp->bFlushed; +} + +/** This method performs outstanding push- and pop- commands. For <SfxShell>s, + which are new on the stack, the <SfxShell::Activate(bool)> is invoked + with bMDI == sal_True, for SfxShells that are removed from the stack, the + <SfxShell::Deactivate(bool)> is invoked with bMDI == sal_True +*/ +void SfxDispatcher::Flush() +{ + if (!xImp->bFlushed) FlushImpl(); +} + +/** With this method, a <SfxShell> pushed on to the SfxDispatcher. + The SfxShell is first marked for push and a timer is set up. + First when the timer has counted down to zero the push + ( <SfxDispatcher::Flush()> ) is actually performed and the + <SfxBindings> is invalidated. While the timer is counting down + the opposing push and pop commands on the same SfxShell are + leveled out. +*/ +void SfxDispatcher::Push(SfxShell& rShell) + +{ + Pop( rShell, SfxDispatcherPopFlags::PUSH ); +} + +/** This method checks whether a particular <SfxShell> instance is + on the SfxDispatcher. + + @returns true The SfxShell instance is on the SfxDispatcher. + false The SfxShell instance is not on the SfxDispatcher. +*/ +bool SfxDispatcher::IsActive(const SfxShell& rShell) + +{ + return CheckVirtualStack(rShell); +} + +/** With this method it can be determined whether the SfxDispatcher is + locked or unlocked. A locked SfxDispatcher does not perform <SfxRequest>s + and no longer provides any status information. It behaves as if all the + slots are disabled. + + The dispatcher is also marked as blocked, if all Dispatcher are locked + (<SfxApplication::LockDispatcher()>) or the associated top frame is in the + modal-mode and if the specified slot are handled as frame-specific + (ie, not served by the application). +*/ +bool SfxDispatcher::IsLocked() const +{ + return xImp->bLocked; +} + +/** With this method it can be determined if the SfxDispacher is the + applications dispatcher. + + @return bool it is the application dispatcher. +*/ +bool SfxDispatcher::IsAppDispatcher() const +{ + return !xImp->pFrame; +} + +/** Helper function to check whether a slot can be executed and + check the execution itself +*/ +void SfxDispatcher::Call_Impl(SfxShell& rShell, const SfxSlot &rSlot, SfxRequest &rReq, bool bRecord) +{ + SFX_STACK(SfxDispatcher::Call_Impl); + + // The slot may be called (meaning enabled) + if ( !rSlot.IsMode(SfxSlotMode::FASTCALL) && !rShell.CanExecuteSlot_Impl(rSlot) && !rShell.IsConditionalFastCall(rReq) ) + return; + + if ( GetFrame() ) + { + // Recording may start + css::uno::Reference< css::beans::XPropertySet > xSet( + GetFrame()->GetFrame().GetFrameInterface(), + css::uno::UNO_QUERY); + + if ( xSet.is() ) + { + css::uno::Any aProp = xSet->getPropertyValue("DispatchRecorderSupplier"); + css::uno::Reference< css::frame::XDispatchRecorderSupplier > xSupplier; + css::uno::Reference< css::frame::XDispatchRecorder > xRecorder; + aProp >>= xSupplier; + if(xSupplier.is()) + xRecorder = xSupplier->getDispatchRecorder(); + + if ( bRecord && xRecorder.is() && !rSlot.IsMode(SfxSlotMode::NORECORD) ) + rReq.Record_Impl( rShell, rSlot, xRecorder, GetFrame() ); + } + } + // Get all that is needed, because the slot may not have survived the + // Execute if it is a 'pseudo slot' for macros or verbs. + bool bAutoUpdate = rSlot.IsMode(SfxSlotMode::AUTOUPDATE); + + // API-call parentheses and document-lock during the calls + { + // 'this' must respond in the Destructor + bool bThisDispatcherAlive = true; + bool *pOldInCallAliveFlag = xImp->pInCallAliveFlag; + xImp->pInCallAliveFlag = &bThisDispatcherAlive; + + SfxExecFunc pFunc = rSlot.GetExecFnc(); + (*pFunc)(&rShell, rReq); + + // If 'this' is still alive + if ( bThisDispatcherAlive ) + xImp->pInCallAliveFlag = pOldInCallAliveFlag; + else + { + if ( pOldInCallAliveFlag ) + { + // also protect nested stack frames + *pOldInCallAliveFlag = false; + } + + // do nothing after this object is dead + return; + } + } + + if ( rReq.IsDone() ) + { + SfxBindings *pBindings = GetBindings(); + + // When AutoUpdate update immediately + if ( bAutoUpdate && pBindings ) + { + pBindings->Invalidate(rSlot.GetSlotId()); + pBindings->Update(rSlot.GetSlotId()); + } + } +} + +void SfxDispatcher::Construct_Impl() +{ + xImp.reset(new SfxDispatcher_Impl); + xImp->bFlushed = true; + + xImp->bFlushing = false; + xImp->bUpdated = false; + xImp->bLocked = false; + xImp->bActive = false; + xImp->bNoUI = false; + xImp->bReadOnly = false; + xImp->bQuiet = false; + xImp->pInCallAliveFlag = nullptr; + xImp->nFilterEnabling = SfxSlotFilterState::DISABLED; + xImp->nDisableFlags = SfxDisableFlags::NONE; + + xImp->bInvalidateOnUnlock = false; + + for (SfxObjectBars_Impl & rObjBar : xImp->aObjBars) + rObjBar.eId = ToolbarId::None; + + xImp->xPoster = new SfxHintPoster(std::bind(&SfxDispatcher::PostMsgHandler, this, std::placeholders::_1)); + + xImp->aIdle.SetPriority(TaskPriority::HIGH_IDLE ); + xImp->aIdle.SetInvokeHandler( LINK(this, SfxDispatcher, EventHdl_Impl ) ); +} + +SfxDispatcher::SfxDispatcher() +{ + Construct_Impl(); + xImp->pFrame = nullptr; +} + +/** The constructor of the SfxDispatcher class places a stack of empty + <SfxShell> pointers. It is not initially locked and is considered flushed. +*/ +SfxDispatcher::SfxDispatcher(SfxViewFrame *pViewFrame) +{ + Construct_Impl(); + xImp->pFrame = pViewFrame; +} + +/** The destructor of the SfxDispatcher class should not be called when the + SfxDispatcher instance is active. It may, however, still be a <SfxShell> + pointer on the stack. +*/ +SfxDispatcher::~SfxDispatcher() +{ + SAL_INFO("sfx.control", "Delete Dispatcher " << reinterpret_cast<sal_Int64>(this)); + DBG_ASSERT( !xImp->bActive, "deleting active Dispatcher" ); + + // So that no timer by Reschedule in PlugComm strikes the LeaveRegistrations + xImp->aIdle.Stop(); + xImp->xPoster->SetEventHdl( std::function<void (std::unique_ptr<SfxRequest>)>() ); + + // Notify the stack variables in Call_Impl + if ( xImp->pInCallAliveFlag ) + *xImp->pInCallAliveFlag = false; + + // Get bindings and application + SfxApplication *pSfxApp = SfxGetpApp(); + SfxBindings* pBindings = GetBindings(); + + // When not flushed, revive the bindings + if (pBindings && !pSfxApp->IsDowning() && !xImp->bFlushed) + pBindings->DLEAVEREGISTRATIONS(); + + // may unregister the bindings + while ( pBindings ) + { + if ( pBindings->GetDispatcher_Impl() == this) + pBindings->SetDispatcher(nullptr); + pBindings = pBindings->GetSubBindings_Impl(); + } +} + +/** With this method, one or more <SfxShell> are popped from the SfxDispatcher. + The SfxShell is marked for popping and a timer is set up. Only when the + timer has reached the end, the pop is actually performed + ( <SfxDispatcher::Flush()> ) and the <SfxBindings> is invalidated. + While the timer is running the opposing push and pop commands on one + SfxShell cancel each other out. + + @param rShell the stack to take the SfxShell instance. + @param nMode SfxDispatcherPopFlags::POP_UNTIL + Also all 'rShell' of SfxShells are taken from the + stack. + + SfxDispatcherPopFlags::POP_DELETE + All SfxShells actually taken from the stack + will be deleted. + + SfxDispatcherPopFlags::PUSH (InPlace use only) + The Shell is pushed. +*/ +void SfxDispatcher::Pop(SfxShell& rShell, SfxDispatcherPopFlags nMode) +{ + DBG_ASSERT( rShell.GetInterface(), + "pushing SfxShell without previous RegisterInterface()" ); + + bool bDelete = bool(nMode & SfxDispatcherPopFlags::POP_DELETE); + bool bUntil = bool(nMode & SfxDispatcherPopFlags::POP_UNTIL); + bool bPush = bool(nMode & SfxDispatcherPopFlags::PUSH); + + SfxApplication *pSfxApp = SfxGetpApp(); + + SAL_INFO( + "sfx.control", + "-SfxDispatcher(" << this << (bPush ? ")::Push(" : ")::Pop(") + << (rShell.GetInterface() + ? rShell.GetInterface()->GetClassName() : SAL_STREAM(&rShell)) + << (bDelete ? ") with delete" : ")") + << (bUntil ? " (up to)" : "")); + + // same shell as on top of the to-do stack? + if(!xImp->aToDoStack.empty() && xImp->aToDoStack.front().pCluster == &rShell) + { + // cancel inverse actions + if ( xImp->aToDoStack.front().bPush != bPush ) + xImp->aToDoStack.pop_front(); + else + { + DBG_ASSERT( bPush, "SfxInterface pushed more than once" ); + DBG_ASSERT( !bPush, "SfxInterface popped more than once" ); + } + } + else + { + // Remember Action + xImp->aToDoStack.push_front( SfxToDo_Impl(bPush, bDelete, bUntil, rShell) ); + if (xImp->bFlushed) + { + SAL_INFO("sfx.control", "Unflushed dispatcher!"); + xImp->bFlushed = false; + xImp->bUpdated = false; + + // Put bindings to sleep + SfxBindings* pBindings = GetBindings(); + if ( pBindings ) + pBindings->DENTERREGISTRATIONS(); + } + } + + if(!pSfxApp->IsDowning() && !xImp->aToDoStack.empty()) + { + // No immediate update is requested + xImp->aIdle.Start(); + } + else + { + // but to do nothing + xImp->aIdle.Stop(); + + // Bindings may wake up again + if(xImp->aToDoStack.empty()) + { + SfxBindings* pBindings = GetBindings(); + if ( pBindings ) + pBindings->DLEAVEREGISTRATIONS(); + } + } +} + + +/** This handler is called after <SfxDispatcher::Invalidate()> or after + changes on the stack (<SfxDispatcher::Push()> and <SfxDispatcher::Pop()) + + It flushes the Stack, if it is dirty, thus it actually executes the + pending Push and Pop commands. +*/ +IMPL_LINK_NOARG( SfxDispatcher, EventHdl_Impl, Timer *, void ) +{ + Flush(); + Update_Impl(); + SfxBindings* pBindings = GetBindings(); + if ( pBindings ) + pBindings->StartUpdate_Impl(); +} + +/** With this method it can be tested whether the <SfxShell> rShell is on the + stack, when it was flushed. This way the SfxDispatcher is not actually + flushed. + + This method is intended among other things to make assertions possible + without the side effect of having to flush the SfxDispatcher. +*/ +bool SfxDispatcher::CheckVirtualStack(const SfxShell& rShell) +{ + SFX_STACK(SfxDispatcher::CheckVirtualStack); + + SfxShellStack_Impl aStack( xImp->aStack ); + for(std::deque<SfxToDo_Impl>::reverse_iterator i = xImp->aToDoStack.rbegin(); i != xImp->aToDoStack.rend(); ++i) + { + if(i->bPush) + aStack.push_back(i->pCluster); + else + { + SfxShell* pPopped(nullptr); + do + { + DBG_ASSERT( !aStack.empty(), "popping from empty stack" ); + pPopped = aStack.back(); + aStack.pop_back(); + } + while(i->bUntil && pPopped != i->pCluster); + DBG_ASSERT(pPopped == i->pCluster, "popping unpushed SfxInterface"); + } + } + + bool bReturn = std::find(aStack.begin(), aStack.end(), &rShell) != aStack.end(); + return bReturn; +} + +/** Determines the position of a given SfxShell in the stack of the dispatcher. + If possible this is flushed before. + + [Return value] + + sal_uInt16 == USRT_MAX + The SfxShell is not on this SfxDispatcher. + + < USHRT_MAX + Position of the SfxShell on the Dispatcher + from the top count stating with 0. +*/ +sal_uInt16 SfxDispatcher::GetShellLevel(const SfxShell& rShell) +{ + SFX_STACK(SfxDispatcher::GetShellLevel); + Flush(); + + for ( size_t n = 0; n < xImp->aStack.size(); ++n ) + if ( *( xImp->aStack.rbegin() + n ) == &rShell ) + return n; + + return USHRT_MAX; +} + +/** Returns a pointer to the <SfxShell> which is at the position nIdx + (from the top, last pushed is 0) on the stack. + + Thus the SfxDispatcher is not flushed. + + Is the stack not deep enough a NULL-Pointer is returned. +*/ +SfxShell *SfxDispatcher::GetShell(sal_uInt16 nIdx) const +{ + sal_uInt16 nShellCount = xImp->aStack.size(); + if ( nIdx < nShellCount ) + return *(xImp->aStack.rbegin() + nIdx); + return nullptr; +} + +/** This method returns a pointer to the <SfxBinding> Instance on which the + SfxDispatcher is currently bound. A SfxDispatcher is only bound to + the SfxBindings when it is <UI-aktiv>. If it is not UI-active, + a NULL-pointer is returned. + + The returned pointer is only valid in the immediate context of the method + call. +*/ +SfxBindings* SfxDispatcher::GetBindings() const +{ + if ( xImp->pFrame ) + return &xImp->pFrame->GetBindings(); + else + return nullptr; +} + +/** Returns a pointer to the <SfxViewFrame> instance, which belongs to + this SfxDispatcher. If it is about the application dispatcher, + a NULL-pointer is returned. +*/ +SfxViewFrame* SfxDispatcher::GetFrame() const +{ + return xImp->pFrame; +} + +/** This method controls the activation of a dispatcher. + + Since the application dispatcher is always active, either as a sub + dispatcher of the <SfxViewFrame> dispatcher or as itself, it is never + activated as a whole, instead only its individual <SfxShell>s at + <SfxDispatcher::Push(SfxShell&)>. + + When activating a SfxDispatcher all of the SfxShells located on its stack + are called with the handler <SfxShell::Activate(bool)>, starting with + the lowest. +*/ +void SfxDispatcher::DoActivate_Impl(bool bMDI) +{ + SFX_STACK(SfxDispatcher::DoActivate); + if ( bMDI ) + { + SAL_INFO("sfx.control", "Activate Dispatcher " << reinterpret_cast<sal_Int64>(this)); + DBG_ASSERT( !xImp->bActive, "Activation error" ); + + xImp->bActive = true; + xImp->bUpdated = false; + SfxBindings* pBindings = GetBindings(); + if ( pBindings ) + { + pBindings->SetDispatcher(this); + pBindings->SetActiveFrame( xImp->pFrame->GetFrame().GetFrameInterface() ); + } + } + else + { + SAL_INFO("sfx.control", "Non-MDI-Activate Dispatcher " << reinterpret_cast<sal_Int64>(this)); + } + + if ( IsAppDispatcher() ) + return; + + for ( int i = int(xImp->aStack.size()) - 1; i >= 0; --i ) + (*(xImp->aStack.rbegin() + i ))->DoActivate_Impl(xImp->pFrame, bMDI); + + if ( bMDI && xImp->pFrame ) + { + xImp->pFrame->GetFrame().GetWorkWindow_Impl()->HidePopups_Impl( false, 1 ); + } + + if(!xImp->aToDoStack.empty()) + { + // No immediate update is requested + xImp->aIdle.Start(); + } +} + +/** This method controls the deactivation of a dispatcher. + + Since the application dispatcher is always active, either as a sub + dispatcher of the <SfxViewFrame> dispatcher or as itself, it is never + deactivated as a whole, instead only its individual <SfxShell>s at + <SfxDispatcher::Pop(SfxShell&)>. + + When deactivating a SfxDispatcher all of the SfxShells located on its stack + are called with the handler <SfxShell::Deactivate(bool)>, starting with + the lowest. +*/ +void SfxDispatcher::DoDeactivate_Impl(bool bMDI, SfxViewFrame const * pNew) +{ + SFX_STACK(SfxDispatcher::DoDeactivate); + + SfxApplication *pSfxApp = SfxGetpApp(); + + if ( bMDI ) + { + SAL_INFO("sfx.control", "Deactivate Dispatcher " << this); + DBG_ASSERT( xImp->bActive, "Deactivate error" ); + xImp->bActive = false; + + if ( xImp->pFrame && !(xImp->pFrame->GetObjectShell()->IsInPlaceActive() ) ) + { + SfxWorkWindow *pWorkWin = xImp->pFrame->GetFrame().GetWorkWindow_Impl(); + if ( pWorkWin ) + { + for (size_t n=0; n<xImp->aChildWins.size();) + { + SfxChildWindow *pWin = pWorkWin->GetChildWindow_Impl( static_cast<sal_uInt16>( xImp->aChildWins[n] & 0xFFFF ) ); + if (!pWin || pWin->GetAlignment() == SfxChildAlignment::NOALIGNMENT) + xImp->aChildWins.erase(xImp->aChildWins.begin()+n); + else + n++; + } + } + } + } + else { + SAL_INFO("sfx.control", "Non-MDI-DeActivate Dispatcher " << this); + } + + if ( IsAppDispatcher() && !pSfxApp->IsDowning() ) + return; + + for ( size_t i = 0; i < xImp->aStack.size(); ++i ) + (*(xImp->aStack.rbegin() + i))->DoDeactivate_Impl(xImp->pFrame, bMDI); + + bool bHidePopups = bMDI && xImp->pFrame; + if ( pNew && xImp->pFrame ) + { + css::uno::Reference< css::frame::XFrame > xOldFrame = + pNew->GetFrame().GetFrameInterface()->getCreator(); + + css::uno::Reference< css::frame::XFrame > xMyFrame = + GetFrame()->GetFrame().GetFrameInterface(); + + if ( xOldFrame == xMyFrame ) + bHidePopups = false; + } + + if ( bHidePopups ) + { + xImp->pFrame->GetFrame().GetWorkWindow_Impl()->HidePopups_Impl( true, 1 ); + } + + Flush(); +} + +/** This method searches in SfxDispatcher after <SfxShell> , from the Slot Id + nSlot currently being handled. For this, the dispatcher is first flushed. + + @param nSlot the searchable Slot-Id + @param ppShell the SfxShell, which are currently handled the nSlot + @param ppSlot the SfxSlot, which are currently handled the nSlot + + @return int sal_True + The SfxShell was found, ppShell and ppSlot are valid. + + sal_False + The SfxShell was not found, ppShell and ppSlot are invalid. +*/ +bool SfxDispatcher::GetShellAndSlot_Impl(sal_uInt16 nSlot, SfxShell** ppShell, + const SfxSlot** ppSlot, bool bOwnShellsOnly, bool bRealSlot) +{ + SFX_STACK(SfxDispatcher::GetShellAndSlot_Impl); + + Flush(); + SfxSlotServer aSvr; + if ( FindServer_(nSlot, aSvr) ) + { + if ( bOwnShellsOnly && aSvr.GetShellLevel() >= xImp->aStack.size() ) + return false; + + *ppShell = GetShell(aSvr.GetShellLevel()); + *ppSlot = aSvr.GetSlot(); + if ( nullptr == (*ppSlot)->GetExecFnc() && bRealSlot ) + *ppSlot = (*ppShell)->GetInterface()->GetRealSlot(*ppSlot); + // Check only real slots as enum slots don't have an execute function! + return !bRealSlot || ((nullptr != *ppSlot) && (nullptr != (*ppSlot)->GetExecFnc()) ); + } + + return false; +} + +/** This method performs a request for a cached <Slot-Server>. + + @param rShell to the calling <SfxShell> + @param rSlot to the calling <SfxSlot> + @param rReq function to be performed (Id and optional parameters) + @param eCallMode Synchronously, asynchronously or as shown in the slot +*/ +void SfxDispatcher::Execute_(SfxShell& rShell, const SfxSlot& rSlot, + SfxRequest& rReq, SfxCallMode eCallMode) +{ + SFX_STACK(SfxDispatcher::Execute_); + DBG_ASSERT( !xImp->bFlushing, "recursive call to dispatcher" ); + DBG_ASSERT( xImp->aToDoStack.empty(), "unprepared InPlace _Execute" ); + + if ( IsLocked() ) + return; + + if ( bool(eCallMode & SfxCallMode::ASYNCHRON) || + ( (eCallMode & SfxCallMode::SYNCHRON) == SfxCallMode::SLOT && + rSlot.IsMode(SfxSlotMode::ASYNCHRON) ) ) + { + sal_uInt16 nShellCount = xImp->aStack.size(); + for ( sal_uInt16 n=0; n<nShellCount; n++ ) + { + if ( &rShell == *(xImp->aStack.rbegin() + n) ) + { + if ( bool(eCallMode & SfxCallMode::RECORD) ) + rReq.AllowRecording( true ); + xImp->xPoster->Post(std::make_unique<SfxRequest>(rReq)); + return; + } + } + } + else + Call_Impl( rShell, rSlot, rReq, SfxCallMode::RECORD==(eCallMode&SfxCallMode::RECORD) ); +} + +/** Helper function to put from rItem below the Which-ID in the pool of the + Item Sets rSet. +*/ +static void MappedPut_Impl(SfxAllItemSet &rSet, const SfxPoolItem &rItem) +{ + // Put with mapped Which-Id if possible + const SfxItemPool *pPool = rSet.GetPool(); + sal_uInt16 nWhich = rItem.Which(); + if ( SfxItemPool::IsSlot(nWhich) ) + nWhich = pPool->GetWhich(nWhich); + rSet.Put( rItem, nWhich ); +} + +const SfxSlot* SfxDispatcher::GetSlot( const OUString& rCommand ) +{ + // Count the number of Shells on the linked Dispatcher + Flush(); + sal_uInt16 nTotCount = xImp->aStack.size(); + + for ( sal_uInt16 i = 0; i < nTotCount; ++i ) + { + SfxShell *pObjShell = GetShell(i); + SfxInterface *pIFace = pObjShell->GetInterface(); + const SfxSlot *pSlot = pIFace->GetSlot( rCommand ); + if ( pSlot ) + return pSlot; + } + + return nullptr; +} + +const SfxPoolItem* SfxDispatcher::Execute(sal_uInt16 nSlot, SfxCallMode nCall, + SfxItemSet const * pArgs, SfxItemSet const * pInternalArgs, sal_uInt16 nModi) +{ + if ( IsLocked() ) + return nullptr; + + SfxShell *pShell = nullptr; + const SfxSlot *pSlot = nullptr; + if ( GetShellAndSlot_Impl( nSlot, &pShell, &pSlot, false, true ) ) + { + SfxAllItemSet aSet( pShell->GetPool() ); + if ( pArgs ) + { + SfxItemIter aIter(*pArgs); + for ( const SfxPoolItem *pArg = aIter.GetCurItem(); + pArg; + pArg = aIter.NextItem() ) + MappedPut_Impl( aSet, *pArg ); + } + SfxRequest aReq(nSlot, nCall, aSet); + if (pInternalArgs) + aReq.SetInternalArgs_Impl( *pInternalArgs ); + aReq.SetModifier( nModi ); + + Execute_( *pShell, *pSlot, aReq, nCall ); + return aReq.GetReturnValue(); + } + return nullptr; +} + +/** Method to execute a <SfxSlot>s over the Slot-Id. + + @param nSlot the Id of the executing function + @param eCall SfxCallMode::SYNCRHON, ..._ASYNCHRON or ..._SLOT + @param pArgs Zero terminated C-Array of Parameters + @param pInternalArgs Zero terminated C-Array of Parameters + + @return const SfxPoolItem* Pointer to the SfxPoolItem valid to the next run + though the Message-Loop, which contains the return + value. + + Or a NULL-Pointer, when the function was not + executed (for example canceled by the user). +*/ +const SfxPoolItem* SfxDispatcher::Execute(sal_uInt16 nSlot, SfxCallMode eCall, + const SfxPoolItem **pArgs, sal_uInt16 nModi, const SfxPoolItem **pInternalArgs) +{ + if ( IsLocked() ) + return nullptr; + + SfxShell *pShell = nullptr; + const SfxSlot *pSlot = nullptr; + if ( GetShellAndSlot_Impl( nSlot, &pShell, &pSlot, false, true ) ) + { + std::unique_ptr<SfxRequest> pReq; + if ( pArgs && *pArgs ) + { + SfxAllItemSet aSet( pShell->GetPool() ); + for ( const SfxPoolItem **pArg = pArgs; *pArg; ++pArg ) + MappedPut_Impl( aSet, **pArg ); + pReq.reset(new SfxRequest( nSlot, eCall, aSet )); + } + else + pReq.reset(new SfxRequest( nSlot, eCall, pShell->GetPool() )); + pReq->SetModifier( nModi ); + if( pInternalArgs && *pInternalArgs) + { + SfxAllItemSet aSet( SfxGetpApp()->GetPool() ); + for ( const SfxPoolItem **pArg = pInternalArgs; *pArg; ++pArg ) + aSet.Put( **pArg ); + pReq->SetInternalArgs_Impl( aSet ); + } + Execute_( *pShell, *pSlot, *pReq, eCall ); + const SfxPoolItem* pRet = pReq->GetReturnValue(); + return pRet; + } + return nullptr; +} + +/** Method to execute a <SfxSlot>s over the Slot-Id. + + @param nSlot the Id of the executing function + @param eCall SfxCallMode::SYNCRHON, ..._ASYNCHRON or ..._SLOT + @param rArgs <SfxItemSet> with the parameters + + @return const SfxPoolItem* Pointer to the SfxPoolItem valid to the next run + though the Message-Loop, which contains the return + value. + + Or a NULL-Pointer, when the function was not + executed (for example canceled by the user). +*/ +const SfxPoolItem* SfxDispatcher::Execute(sal_uInt16 nSlot, SfxCallMode eCall, + const SfxItemSet &rArgs) +{ + if ( IsLocked() ) + return nullptr; + + SfxShell *pShell = nullptr; + const SfxSlot *pSlot = nullptr; + if ( GetShellAndSlot_Impl( nSlot, &pShell, &pSlot, false, true ) ) + { + SfxAllItemSet aSet( pShell->GetPool() ); + SfxItemIter aIter(rArgs); + for ( const SfxPoolItem *pArg = aIter.GetCurItem(); + pArg; + pArg = aIter.NextItem() ) + MappedPut_Impl( aSet, *pArg ); + SfxRequest aReq( nSlot, eCall, aSet ); + aReq.SetModifier( 0 ); + Execute_( *pShell, *pSlot, aReq, eCall ); + return aReq.GetReturnValue(); + } + return nullptr; +} + +/** Method to execute a <SfxSlot>s over the Slot-Id. + + [Note] + + The parameters are copied, can therefore be passed on as the address + of stack objects. + + @param nSlot the Id of the executing function + @param eCall SfxCallMode::SYNCRHON, ..._ASYNCHRON or ..._SLOT + @param args list of SfxPoolItem arguments + + @return Pointer to the SfxPoolItem valid to the next run + though the Message-Loop, which contains the return + value. + + Or a NULL-Pointer, when the function was not + executed (for example canceled by the user). + + [Example] + + pDispatcher->Execute( SID_OPENDOCUMENT, SfxCallMode::SYNCHRON, + { &SfxStringItem( SID_FILE_NAME, "\\tmp\\temp.sdd" ), + &SfxStringItem( SID_FILTER_NAME, "StarDraw Presentation" ), + &SfxBoolItem( SID_DOC_READONLY, sal_False ), + }); +*/ +const SfxPoolItem* SfxDispatcher::ExecuteList(sal_uInt16 nSlot, SfxCallMode eCall, + std::initializer_list<SfxPoolItem const*> args, + std::initializer_list<SfxPoolItem const*> internalargs) +{ + if ( IsLocked() ) + return nullptr; + + SfxShell *pShell = nullptr; + const SfxSlot *pSlot = nullptr; + if ( GetShellAndSlot_Impl( nSlot, &pShell, &pSlot, false, true ) ) + { + SfxAllItemSet aSet( pShell->GetPool() ); + + for (const SfxPoolItem *pArg : args) + { + assert(pArg); + MappedPut_Impl( aSet, *pArg ); + } + + SfxRequest aReq(nSlot, eCall, aSet); + + if (internalargs.begin() != internalargs.end()) + { + SfxAllItemSet aInternalSet(SfxGetpApp()->GetPool()); + for (const SfxPoolItem *pArg : internalargs) + { + assert(pArg); + aInternalSet.Put(*pArg); + } + aReq.SetInternalArgs_Impl(aInternalSet); + } + + Execute_( *pShell, *pSlot, aReq, eCall ); + return aReq.GetReturnValue(); + } + return nullptr; +} + +/** Helper method to receive the asynchronously executed <SfxRequest>s. +*/ +void SfxDispatcher::PostMsgHandler(std::unique_ptr<SfxRequest> pReq) +{ + DBG_ASSERT( !xImp->bFlushing, "recursive call to dispatcher" ); + SFX_STACK(SfxDispatcher::PostMsgHandler); + + // Has also the Pool not yet died? + if ( pReq->IsCancelled() ) + return; + + if ( !IsLocked() ) + { + Flush(); + SfxSlotServer aSvr; + if ( FindServer_(pReq->GetSlot(), aSvr ) ) // HACK(x), whatever that was supposed to mean + { + const SfxSlot *pSlot = aSvr.GetSlot(); + SfxShell *pSh = GetShell(aSvr.GetShellLevel()); + + // When the pSlot is a "Pseudoslot" for macros or Verbs, it can + // be destroyed in the Call_Impl, thus do not use it anymore! + pReq->SetSynchronCall( false ); + Call_Impl( *pSh, *pSlot, *pReq, pReq->AllowsRecording() ); //! why bRecord? + } + } + else + { + if ( xImp->bLocked ) + xImp->aReqArr.emplace_back(std::move(pReq)); + else + xImp->xPoster->Post(std::move(pReq)); + } +} + +void SfxDispatcher::SetMenu_Impl() +{ +#if HAVE_FEATURE_DESKTOP + if ( !xImp->pFrame ) + return; + + SfxViewFrame* pTop = xImp->pFrame->GetTopViewFrame(); + if ( !pTop || pTop->GetBindings().GetDispatcher() != this ) + return; + + SfxFrame& rFrame = pTop->GetFrame(); + if ( !rFrame.IsMenuBarOn_Impl() ) + return; + + css::uno::Reference < css::beans::XPropertySet > xPropSet( rFrame.GetFrameInterface(), css::uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager; + css::uno::Any aValue = xPropSet->getPropertyValue("LayoutManager"); + aValue >>= xLayoutManager; + if ( xLayoutManager.is() ) + { + OUString aMenuBarURL( "private:resource/menubar/menubar" ); + if ( !xLayoutManager->isElementVisible( aMenuBarURL ) ) + xLayoutManager->createElement( aMenuBarURL ); + } + } +#endif +} + +void SfxDispatcher::Update_Impl( bool bForce ) +{ + SFX_STACK(SfxDispatcher::Update_Impl); + + Flush(); + + if ( !xImp->pFrame ) + return; + + bool bUpdate = bForce; + if ( xImp->pFrame ) + { + SfxWorkWindow *pWork = xImp->pFrame->GetFrame().GetWorkWindow_Impl(); + SfxDispatcher *pAct = pWork->GetBindings().GetDispatcher_Impl(); + if (pAct == this) + { + if ( !bUpdate ) + bUpdate = !xImp->bUpdated; + xImp->bUpdated = true; + } + } + + if ( !bUpdate || xImp->pFrame->GetFrame().IsClosing_Impl() ) + return; + + SfxViewFrame* pTop = xImp->pFrame ? xImp->pFrame->GetTopViewFrame() : nullptr; + bool bUIActive = pTop && pTop->GetBindings().GetDispatcher() == this; + + if ( !bUIActive && pTop && GetBindings() == &pTop->GetBindings() ) + // keep own tools internally for collecting + GetBindings()->GetDispatcher()->xImp->bUpdated = false; + + css::uno::Reference< css::frame::XFrame > xFrame; + SfxBindings* pBindings = GetBindings(); + if (pBindings) + { + pBindings->DENTERREGISTRATIONS(); + xFrame = pBindings->GetActiveFrame(); + } + css::uno::Reference< css::beans::XPropertySet > xPropSet( xFrame, css::uno::UNO_QUERY ); + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager; + if ( xPropSet.is() ) + { + try + { + css::uno::Any aValue = xPropSet->getPropertyValue("LayoutManager"); + aValue >>= xLayoutManager; + } + catch (const css::uno::Exception&) + { + } + } + + if ( xLayoutManager.is() ) + xLayoutManager->lock(); + + bool bIsIPActive = xImp->pFrame && xImp->pFrame->GetObjectShell()->IsInPlaceActive(); + SfxInPlaceClient *pClient = xImp->pFrame ? xImp->pFrame->GetViewShell()->GetUIActiveClient() : nullptr; + if ( bUIActive && /* !bIsIPActive && */ ( !pClient || !pClient->IsObjectUIActive() ) ) + SetMenu_Impl(); + + SfxWorkWindow *pWorkWin = xImp->pFrame->GetFrame().GetWorkWindow_Impl(); + pWorkWin->ResetStatusBar_Impl(); + + { + SfxWorkWindow *pWork = xImp->pFrame->GetFrame().GetWorkWindow_Impl(); + SfxDispatcher *pAct = pWork->GetBindings().GetDispatcher_Impl(); + if (pAct == this) + { + pWork->ResetObjectBars_Impl(); + pWork->ResetChildWindows_Impl(); + } + } + + bool bIsActive = false; + SfxDispatcher *pActDispat = pWorkWin->GetBindings().GetDispatcher_Impl(); + if ( !bIsActive && this == pActDispat ) + bIsActive = true; + + Update_Impl_( bUIActive, !bIsIPActive, bIsIPActive, pWorkWin ); + if (bUIActive || bIsActive) + pWorkWin->UpdateObjectBars_Impl(); + + if ( pBindings ) + pBindings->DLEAVEREGISTRATIONS(); + + if ( xLayoutManager.is() ) + xLayoutManager->unlock(); + + if ( SfxViewShell::Current() && SfxViewShell::Current()->GetDispatcher() ) + { + const SfxPoolItem *pItem; + SfxViewShell::Current()->GetDispatcher()->QueryState(SID_NOTEBOOKBAR, pItem); + } +} + +void SfxDispatcher::Update_Impl_( bool bUIActive, bool bIsMDIApp, bool bIsIPOwner, SfxWorkWindow *pTaskWin ) +{ + SfxWorkWindow *pWorkWin = xImp->pFrame->GetFrame().GetWorkWindow_Impl(); + bool bIsActive = false; + SfxDispatcher *pActDispat = pWorkWin->GetBindings().GetDispatcher_Impl(); + if ( pActDispat && !bIsActive ) + { + if ( this == pActDispat ) + bIsActive = true; + } + + for (SfxObjectBars_Impl & rObjBar : xImp->aObjBars) + rObjBar.eId = ToolbarId::None; + xImp->aChildWins.clear(); + + // bQuiet: own shells aren't considered for UI and SlotServer + // bNoUI: own Shells aren't considered forms UI + if ( xImp->bQuiet || xImp->bNoUI || (xImp->pFrame && xImp->pFrame->GetObjectShell()->IsPreview()) ) + return; + + StatusBarId eStatBarId = StatusBarId::None; + + SfxSlotPool* pSlotPool = &SfxSlotPool::GetSlotPool( GetFrame() ); + sal_uInt16 nTotCount = xImp->aStack.size(); + for ( sal_uInt16 nShell = nTotCount; nShell > 0; --nShell ) + { + SfxShell *pShell = GetShell( nShell-1 ); + SfxInterface *pIFace = pShell->GetInterface(); + + // don't consider shells if "Hidden" or "Quiet" + bool bReadOnlyShell = IsReadOnlyShell_Impl( nShell-1 ); + sal_uInt16 nNo; + for ( nNo = 0; pIFace && nNo<pIFace->GetObjectBarCount(); ++nNo ) + { + sal_uInt16 nPos = pIFace->GetObjectBarPos(nNo); + SfxVisibilityFlags nFlags = pIFace->GetObjectBarFlags(nNo); + if ( bReadOnlyShell && !( nFlags & SfxVisibilityFlags::ReadonlyDoc ) ) + continue; + + // check whether toolbar needs activation of a special feature + SfxShellFeature nFeature = pIFace->GetObjectBarFeature(nNo); + if ((nFeature != SfxShellFeature::NONE) && !pShell->HasUIFeature(nFeature)) + continue; + + // check for toolboxes that are exclusively for a viewer + if ( xImp->pFrame) + { + bool bViewerTbx( nFlags & SfxVisibilityFlags::Viewer ); + SfxObjectShell* pSh = xImp->pFrame->GetObjectShell(); + const SfxBoolItem* pItem = SfxItemSet::GetItem<SfxBoolItem>(pSh->GetMedium()->GetItemSet(), SID_VIEWONLY, false); + bool bIsViewer = pItem && pItem->GetValue(); + if ( bIsViewer != bViewerTbx ) + continue; + } + + // always register toolbars, allows to switch them on + bool bVisible = pIFace->IsObjectBarVisible(nNo); + if ( !bVisible ) + nFlags = SfxVisibilityFlags::Invisible; + + SfxObjectBars_Impl& rBar = xImp->aObjBars[nPos]; + rBar.nPos = nPos; + rBar.nFlags = nFlags; + rBar.eId = pIFace->GetObjectBarId(nNo); + + if ( bUIActive || bIsActive ) + { + pWorkWin->SetObjectBar_Impl(nPos, nFlags, rBar.eId); + } + + if ( !bVisible ) + rBar.eId = ToolbarId::None; + } + + for ( nNo=0; pIFace && nNo<pIFace->GetChildWindowCount(); nNo++ ) + { + sal_uInt32 nId = pIFace->GetChildWindowId(nNo); + const SfxSlot *pSlot = pSlotPool->GetSlot( static_cast<sal_uInt16>(nId) ); + SAL_WARN_IF( !pSlot, "sfx.control", "Childwindow slot missing: " << nId ); + if ( bReadOnlyShell ) + { + // only show ChildWindows if their slot is allowed for readonly documents + if ( pSlot && !pSlot->IsMode( SfxSlotMode::READONLYDOC ) ) + continue; + } + + SfxShellFeature nFeature = pIFace->GetChildWindowFeature(nNo); + if ((nFeature != SfxShellFeature::NONE) && !pShell->HasUIFeature(nFeature)) + continue; + + // slot decides whether a ChildWindow is shown when document is OLE server or OLE client + SfxVisibilityFlags nMode = SfxVisibilityFlags::Standard; + if( pSlot ) + { + if ( pSlot->IsMode(SfxSlotMode::CONTAINER) ) + { + if ( pWorkWin->IsVisible_Impl( SfxVisibilityFlags::Client ) ) + nMode |= SfxVisibilityFlags::Client; + } + else + { + if ( pWorkWin->IsVisible_Impl( SfxVisibilityFlags::Server ) ) + nMode |= SfxVisibilityFlags::Server; + } + } + + if ( bUIActive || bIsActive ) + pWorkWin->SetChildWindowVisible_Impl( nId, true, nMode ); + if ( bUIActive || bIsActive || !pWorkWin->IsFloating( static_cast<sal_uInt16>( nId & 0xFFFF ) ) ) + xImp->aChildWins.push_back( nId ); + } + + if ( bIsMDIApp || bIsIPOwner ) + { + StatusBarId eId = pIFace ? pIFace->GetStatusBarId() : StatusBarId::None; + if (eId != StatusBarId::None) + eStatBarId = eId; + } + } + + for ( sal_uInt16 nPos=0; nPos<SFX_OBJECTBAR_MAX; nPos++ ) + { + SfxObjectBars_Impl& rFixed = xImp->aFixedObjBars[nPos]; + if (rFixed.eId != ToolbarId::None) + { + SfxObjectBars_Impl& rBar = xImp->aObjBars[nPos]; + rBar = rFixed; + pWorkWin->SetObjectBar_Impl(rFixed.nPos, rFixed.nFlags, + rFixed.eId); + } + } + + if ( !pTaskWin || ( !bIsMDIApp && !bIsIPOwner ) ) + return; + + bool bIsTaskActive = false; + + SfxDispatcher *pActDispatcher = pTaskWin->GetBindings().GetDispatcher_Impl(); + if ( pActDispatcher && !bIsTaskActive ) + { + if ( this == pActDispatcher ) + bIsTaskActive = true; + } + + if (bIsTaskActive && eStatBarId != StatusBarId::None && xImp->pFrame) + { + // internal frames also may control statusbar + xImp->pFrame->GetFrame().GetWorkWindow_Impl()->SetStatusBar_Impl(eStatBarId); + } +} + +/** Helper method to execute the outstanding push and pop commands. +*/ +void SfxDispatcher::FlushImpl() +{ + SFX_STACK(SfxDispatcher::FlushImpl); + + SAL_INFO("sfx.control", "Flushing dispatcher!"); + + xImp->aIdle.Stop(); + + xImp->bFlushing = !xImp->bFlushing; + if ( !xImp->bFlushing ) + { + xImp->bFlushing = true; + return; + } + + SfxApplication *pSfxApp = SfxGetpApp(); + + // Re-build the true stack in the first round + std::deque<SfxToDo_Impl> aToDoCopy; + bool bModify = false; + for(std::deque<SfxToDo_Impl>::reverse_iterator i = xImp->aToDoStack.rbegin(); i != xImp->aToDoStack.rend(); ++i) + { + bModify = true; + + if(i->bPush) + { + // Actually push + DBG_ASSERT( std::find(xImp->aStack.begin(), xImp->aStack.end(), i->pCluster) == xImp->aStack.end(), + "pushed SfxShell already on stack" ); + xImp->aStack.push_back(i->pCluster); + i->pCluster->SetDisableFlags(xImp->nDisableFlags); + + // Mark the moved shell + aToDoCopy.push_front(*i); + } + else + { + // Actually pop + SfxShell* pPopped = nullptr; + bool bFound = false; + do + { + DBG_ASSERT( !xImp->aStack.empty(), "popping from empty stack" ); + pPopped = xImp->aStack.back(); + xImp->aStack.pop_back(); + pPopped->SetDisableFlags( SfxDisableFlags::NONE ); + bFound = (pPopped == i->pCluster); + + // Mark the moved Shell + aToDoCopy.push_front(SfxToDo_Impl(false, i->bDelete, false, *pPopped)); + } + while(i->bUntil && !bFound); + DBG_ASSERT( bFound, "wrong SfxShell popped" ); + } + } + xImp->aToDoStack.clear(); + + // Invalidate bindings, if possible + if ( !pSfxApp->IsDowning() ) + { + InvalidateBindings_Impl( bModify ); + } + + xImp->bFlushing = false; + xImp->bUpdated = false; // not only when bModify, if Doc/Template-Config + xImp->bFlushed = true; + SAL_INFO("sfx.control", "Successfully flushed dispatcher!"); + + //fdo#70703 FlushImpl may call back into itself so use aToDoCopyStack to talk + //to outer levels of ourself. If DoActivate_Impl/DoDeactivate_Impl deletes + //an entry, then they will walk back up aToDoCopyStack and set outer + //levels's entries to bDeleted + xImp->aToDoCopyStack.push_back(aToDoCopy); + std::deque<SfxToDo_Impl>& rToDoCopy = xImp->aToDoCopyStack.back(); + // Activate the Shells and possible delete them in the 2nd round + for(std::deque<SfxToDo_Impl>::reverse_iterator i = rToDoCopy.rbegin(); i != rToDoCopy.rend(); ++i) + { + if (i->bDeleted) + continue; + if (!xImp->bActive) + continue; + if (i->bPush) + i->pCluster->DoActivate_Impl(xImp->pFrame, true); + else + i->pCluster->DoDeactivate_Impl(xImp->pFrame, true); + } + + aToDoCopy = xImp->aToDoCopyStack.back(); + xImp->aToDoCopyStack.pop_back(); + + for(std::deque<SfxToDo_Impl>::reverse_iterator i = aToDoCopy.rbegin(); i != aToDoCopy.rend(); ++i) + { + if (i->bDelete && !i->bDeleted) + { + if (!xImp->aToDoCopyStack.empty()) + { + //fdo#70703 if there is an outer FlushImpl then inform it that + //we have deleted this cluster + for (auto & elem : xImp->aToDoCopyStack) + { + for (auto & subelem : elem) + { + if (subelem.pCluster == i->pCluster) + subelem.bDeleted = true; + } + } + } + delete i->pCluster; + } + } + bool bAwakeBindings = !aToDoCopy.empty(); + if( bAwakeBindings ) + aToDoCopy.clear(); + + // If more changes have occurred on the stack when + // Activate/Deactivate/Delete: + if (!xImp->bFlushed) + // If Push/Pop has been called by someone, then also EnterReg was called! + FlushImpl(); + + if( bAwakeBindings && GetBindings() ) + GetBindings()->DLEAVEREGISTRATIONS(); + + for (SfxObjectBars_Impl & rFixedObjBar : xImp->aFixedObjBars) + rFixedObjBar.eId = ToolbarId::None; + + SAL_INFO("sfx.control", "SfxDispatcher(" << this << ")::Flush() done"); +} + +/** With this method a filter set, the target slots can be enabled or disabled. + The passed array must be retained until the destructor or the next + <SetSlotFilter()>, it is not deleted from the dispatcher, so it can thus be + static. + + In read-only documents the quasi ReadOnlyDoc Flag of slots can be + overturned by the use of 'bEnable == 2', so this will be displayed again. + On the other slots it has no effect. + + // HACK(here should be used an enum) ??? + @param nEnable 1==true: only enable specified slots, disable all other + 0==false: disable specified slots, first enable all other + @param nCount Number of SIDs in the following Array + @param pSIDs sorted Array of 'nCount' SIDs + + [Example] + + Targeted disabling of Slots 1, 2 and 3: + + static sal_uInt16 const pSIDs[] = { 1, 2, 3 }; + pDisp->SetSlotFilter( sal_False, sizeof(pSIDs)/sizeof(sal_uInt16), pSIDs ); + + only permit Slots 5, 6 and 7: + + static sal_uInt16 const pSIDs[] = { 5, 6, 7 }; + pDisp->SetSlotFilter( sal_True, sizeof(pSIDs)/sizeof(sal_uInt16), pSIDs ); + + Turn-off Filter: + + pDisp->SetSlotFilter(); +*/ +void SfxDispatcher::SetSlotFilter(SfxSlotFilterState nEnable, + o3tl::span<sal_uInt16 const> pSIDs) +{ +#ifdef DBG_UTIL + // Check Array + for ( std::size_t n = 1; n < pSIDs.size(); ++n ) + DBG_ASSERT( pSIDs[n] > pSIDs[n-1], "SetSlotFilter: SIDs not sorted" ); +#endif + + xImp->nFilterEnabling = nEnable; + xImp->pFilterSIDs = pSIDs; + + GetBindings()->InvalidateAll(true); +} + +extern "C" { + +static int SfxCompareSIDs_Impl(const void* pSmaller, const void* pBigger) +{ + return static_cast<tools::Long>(*static_cast<sal_uInt16 const *>(pSmaller)) - static_cast<tools::Long>(*static_cast<sal_uInt16 const *>(pBigger)); +} + +} + +/** Searches for 'nSID' in the Filter set by <SetSlotFilter()> and + returns sal_True, if the SIDis allowed, or sal_False, if it is + disabled by the Filter. + + @return 0 => disabled + 1 => enabled + 2 => enabled even if ReadOnlyDoc +*/ +SfxSlotFilterState SfxDispatcher::IsSlotEnabledByFilter_Impl( sal_uInt16 nSID ) const +{ + // no filter? + if ( xImp->pFilterSIDs.empty() ) + // => all SIDs allowed + return SfxSlotFilterState::ENABLED; + + // search + bool bFound = nullptr != bsearch( &nSID, xImp->pFilterSIDs.data(), xImp->pFilterSIDs.size(), + sizeof(sal_uInt16), SfxCompareSIDs_Impl ); + + // even if ReadOnlyDoc + if ( SfxSlotFilterState::ENABLED_READONLY == xImp->nFilterEnabling ) + return bFound ? SfxSlotFilterState::ENABLED_READONLY : SfxSlotFilterState::ENABLED; + // Otherwise after Negative/Positive Filter + else if ( SfxSlotFilterState::ENABLED == xImp->nFilterEnabling ) + return bFound ? SfxSlotFilterState::ENABLED : SfxSlotFilterState::DISABLED; + else + return bFound ? SfxSlotFilterState::DISABLED : SfxSlotFilterState::ENABLED; +} + +/** This helper method searches for the <Slot-Server> which currently serves + the nSlot. As the result, rServe is filled accordingly. + + If known the SfxInterface which is currently served by nSlot can be + passed along. + + The SfxDispatcher is flushed while searching for nSlot. + + @param nSlot Slot-Id to search for + @param rServer <SfxSlotServer>-Instance to fill + + @return true + The Slot was found, rServer is valid. + + false + The Slot is currently not served, rServer is invalid. +*/ +bool SfxDispatcher::FindServer_(sal_uInt16 nSlot, SfxSlotServer& rServer) +{ + SFX_STACK(SfxDispatcher::FindServer_); + + // Dispatcher locked? (nevertheless let SID_HELP_PI through) + if ( IsLocked() ) + { + xImp->bInvalidateOnUnlock = true; + return false; + } + + // Count the number of Shells in the linked dispatchers. + Flush(); + sal_uInt16 nTotCount = xImp->aStack.size(); + + // Verb-Slot? + if (nSlot >= SID_VERB_START && nSlot <= SID_VERB_END) + { + for ( sal_uInt16 nShell = 0;; ++nShell ) + { + SfxShell *pSh = GetShell(nShell); + if ( pSh == nullptr ) + return false; + if ( dynamic_cast< const SfxViewShell *>( pSh ) != nullptr ) + { + const SfxSlot* pSlot = pSh->GetVerbSlot_Impl(nSlot); + if ( pSlot ) + { + rServer.SetShellLevel(nShell); + rServer.SetSlot( pSlot ); + return true; + } + } + } + } + + // SID check against set filter + SfxSlotFilterState nSlotEnableMode = SfxSlotFilterState::DISABLED; + if ( xImp->pFrame ) + { + nSlotEnableMode = IsSlotEnabledByFilter_Impl( nSlot ); + if ( SfxSlotFilterState::DISABLED == nSlotEnableMode ) + return false; + } + + // In Quiet-Mode only Parent-Dispatcher + if ( xImp->bQuiet ) + { + return false; + } + + bool bReadOnly = ( SfxSlotFilterState::ENABLED_READONLY != nSlotEnableMode && xImp->bReadOnly ); + + // search through all the shells of the chained dispatchers + // from top to bottom + sal_uInt16 nFirstShell = 0; + for ( sal_uInt16 i = nFirstShell; i < nTotCount; ++i ) + { + SfxShell *pObjShell = GetShell(i); + SfxInterface *pIFace = pObjShell->GetInterface(); + const SfxSlot *pSlot = pIFace->GetSlot(nSlot); + + if ( pSlot && pSlot->nDisableFlags != SfxDisableFlags::NONE && + ( static_cast<int>(pSlot->nDisableFlags) & static_cast<int>(pObjShell->GetDisableFlags()) ) != 0 ) + return false; + + if ( pSlot && !( pSlot->nFlags & SfxSlotMode::READONLYDOC ) && bReadOnly ) + return false; + + if ( pSlot ) + { + // Slot belongs to Container? + bool bIsContainerSlot = pSlot->IsMode(SfxSlotMode::CONTAINER); + bool bIsInPlace = xImp->pFrame && xImp->pFrame->GetObjectShell()->IsInPlaceActive(); + + // Shell belongs to Server? + // AppDispatcher or IPFrame-Dispatcher + bool bIsServerShell = !xImp->pFrame || bIsInPlace; + + // Of course ShellServer-Slots are also executable even when it is + // executed on a container dispatcher without an IPClient. + if ( !bIsServerShell ) + { + SfxViewShell *pViewSh = xImp->pFrame->GetViewShell(); + bIsServerShell = !pViewSh || !pViewSh->GetUIActiveClient(); + } + + // Shell belongs to Container? + // AppDispatcher or no IPFrameDispatcher + bool bIsContainerShell = !xImp->pFrame || !bIsInPlace; + // Shell and Slot match + if ( !( ( bIsContainerSlot && bIsContainerShell ) || + ( !bIsContainerSlot && bIsServerShell ) ) ) + pSlot = nullptr; + } + + if ( pSlot ) + { + rServer.SetSlot(pSlot); + rServer.SetShellLevel(i); + return true; + } + } + + return false; +} + +/** Helper method to obtain the status of the <Slot-Server>s rSvr. + The required slots IDs (partly converted to Which-IDs of the pool) + must be present in rstate. + + The SfxDispatcher is flushed before the query. + + @param rSvr Slot-Server to query + @param rState SfxItemSet to be filled + @param pRealSlot The actual Slot if possible +*/ +bool SfxDispatcher::FillState_(const SfxSlotServer& rSvr, SfxItemSet& rState, + const SfxSlot* pRealSlot) +{ + SFX_STACK(SfxDispatcher::FillState_); + + const SfxSlot *pSlot = rSvr.GetSlot(); + if ( pSlot && IsLocked() ) + { + xImp->bInvalidateOnUnlock = true; + return false; + } + + if ( pSlot ) + { + DBG_ASSERT(xImp->bFlushed, + "Dispatcher not flushed after retrieving slot servers!"); + if (!xImp->bFlushed) + return false; + + // Determine the object and call the Message of this object + SfxShell *pSh = GetShell(rSvr.GetShellLevel()); + DBG_ASSERT(pSh, "ObjectShell not found"); + + SfxStateFunc pFunc; + + if (pRealSlot) + pFunc = pRealSlot->GetStateFnc(); + else + pFunc = pSlot->GetStateFnc(); + + (*pFunc)(pSh, rState); +#ifdef DBG_UTIL + // To examine the conformity of IDL (SlotMap) and current Items + if ( rState.Count() ) + { + SfxInterface *pIF = pSh->GetInterface(); + SfxItemIter aIter( rState ); + for ( const SfxPoolItem *pItem = aIter.GetCurItem(); + pItem; + pItem = aIter.NextItem() ) + { + if ( !IsInvalidItem(pItem) && !pItem->IsVoidItem() ) + { + sal_uInt16 nSlotId = rState.GetPool()->GetSlotId(pItem->Which()); + SAL_INFO_IF( + typeid(pItem) != *pIF->GetSlot(nSlotId)->GetType()->Type(), + "sfx.control", + "item-type unequal to IDL (=> no BASIC) with SID: " + << nSlotId << " in " << pIF->GetClassName()); + } + } + } +#endif + + return true; + } + + return false; +} + +void SfxDispatcher::ExecutePopup( vcl::Window *pWin, const Point *pPos ) +{ + SfxDispatcher &rDisp = *SfxGetpApp()->GetDispatcher_Impl(); + sal_uInt16 nShLevel = 0; + SfxShell *pSh; + + if ( rDisp.xImp->bQuiet ) + nShLevel = rDisp.xImp->aStack.size(); + + for ( pSh = rDisp.GetShell(nShLevel); pSh; ++nShLevel, pSh = rDisp.GetShell(nShLevel) ) + { + const OUString& rResName = pSh->GetInterface()->GetPopupMenuName(); + if ( !rResName.isEmpty() ) + { + rDisp.ExecutePopup( rResName, pWin, pPos ); + return; + } + } +} + +namespace { + +boost::property_tree::ptree fillPopupMenu(Menu* pMenu) +{ + // Activate this menu first + pMenu->HandleMenuActivateEvent(pMenu); + pMenu->HandleMenuDeActivateEvent(pMenu); + + boost::property_tree::ptree aTree; + // If last item inserted is some valid text + bool bIsLastItemText = false; + sal_uInt16 nCount = pMenu->GetItemCount(); + for (sal_uInt16 nPos = 0; nPos < nCount; nPos++) + { + boost::property_tree::ptree aItemTree; + const MenuItemType aItemType = pMenu->GetItemType(nPos); + + if (aItemType == MenuItemType::DONTKNOW) + continue; + + if (aItemType == MenuItemType::SEPARATOR) + { + if (bIsLastItemText) + aItemTree.put("type", "separator"); + bIsLastItemText = false; + } + else + { + const sal_uInt16 nItemId = pMenu->GetItemId(nPos); + OUString aCommandURL = pMenu->GetItemCommand(nItemId); + + if (aCommandURL.isEmpty()) + { + const SfxSlot *pSlot = SFX_SLOTPOOL().GetSlot(nItemId); + if (pSlot) + aCommandURL = pSlot->GetCommandString(); + } + + const OUString aItemText = pMenu->GetItemText(nItemId); + Menu* pPopupSubmenu = pMenu->GetPopupMenu(nItemId); + + if (!aItemText.isEmpty()) + aItemTree.put("text", aItemText.toUtf8().getStr()); + + if (pPopupSubmenu) + { + boost::property_tree::ptree aSubmenu = ::fillPopupMenu(pPopupSubmenu); + if (aSubmenu.empty()) + continue; + + aItemTree.put("type", "menu"); + if (!aCommandURL.isEmpty()) + aItemTree.put("command", aCommandURL.toUtf8().getStr()); + aItemTree.push_back(std::make_pair("menu", aSubmenu)); + } + else + { + // no point in exposing choices that don't have the .uno: + // command + if (aCommandURL.isEmpty()) + continue; + + aItemTree.put("type", "command"); + aItemTree.put("command", aCommandURL.toUtf8().getStr()); + } + + aItemTree.put("enabled", pMenu->IsItemEnabled(nItemId)); + + MenuItemBits aItemBits = pMenu->GetItemBits(nItemId); + bool bHasChecks = true; + if (aItemBits & MenuItemBits::CHECKABLE) + aItemTree.put("checktype", "checkmark"); + else if (aItemBits & MenuItemBits::RADIOCHECK) + aItemTree.put("checktype", "radio"); + else if (aItemBits & MenuItemBits::AUTOCHECK) + aItemTree.put("checktype", "auto"); + else + bHasChecks = false; + + if (bHasChecks) + aItemTree.put("checked", pMenu->IsItemChecked(nItemId)); + } + + if (!aItemTree.empty()) + { + aTree.push_back(std::make_pair("", aItemTree)); + if (aItemType != MenuItemType::SEPARATOR) + bIsLastItemText = true; + } + } + + return aTree; +} + +} + +boost::property_tree::ptree SfxDispatcher::fillPopupMenu(const css::uno::Reference<css::awt::XPopupMenu>& rPopupMenu) +{ + VCLXMenu* pAwtMenu = comphelper::getFromUnoTunnel<VCLXMenu>(rPopupMenu); + PopupMenu* pVCLMenu = static_cast<PopupMenu*>(pAwtMenu->GetMenu()); + return ::fillPopupMenu(pVCLMenu); +} + +void SfxDispatcher::ExecutePopup( const OUString& rResName, vcl::Window* pWin, const Point* pPos ) +{ + css::uno::Sequence< css::uno::Any > aArgs{ + css::uno::Any(comphelper::makePropertyValue( "Value", rResName )), + css::uno::Any(comphelper::makePropertyValue( "Frame", GetFrame()->GetFrame().GetFrameInterface() )), + css::uno::Any(comphelper::makePropertyValue( "IsContextMenu", true )) + }; + + css::uno::Reference< css::uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + css::uno::Reference< css::frame::XPopupMenuController > xPopupController( + xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.framework.ResourceMenuController", aArgs, xContext ), css::uno::UNO_QUERY ); + + css::uno::Reference< css::awt::XPopupMenu > xPopupMenu( xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.awt.PopupMenu", xContext ), css::uno::UNO_QUERY ); + + if ( !xPopupController.is() || !xPopupMenu.is() ) + return; + + vcl::Window* pWindow = pWin ? pWin : xImp->pFrame->GetFrame().GetWorkWindow_Impl()->GetWindow(); + Point aPos = pPos ? *pPos : pWindow->GetPointerPosPixel(); + + css::ui::ContextMenuExecuteEvent aEvent; + aEvent.SourceWindow = VCLUnoHelper::GetInterface( pWindow ); + aEvent.ExecutePosition.X = aPos.X(); + aEvent.ExecutePosition.Y = aPos.Y(); + + xPopupController->setPopupMenu( xPopupMenu ); + if (comphelper::LibreOfficeKit::isActive()) + { + boost::property_tree::ptree aMenu = fillPopupMenu(xPopupMenu); + boost::property_tree::ptree aRoot; + aRoot.add_child("menu", aMenu); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aRoot, true); + if (SfxViewShell* pViewShell = xImp->pFrame->GetViewShell()) + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_MENU, aStream.str().c_str()); + } + else + { + OUString aMenuURL = "private:resource/popupmenu/" + rResName; + if (GetFrame()->GetViewShell()->TryContextMenuInterception(xPopupMenu, aMenuURL, aEvent)) + { + css::uno::Reference<css::awt::XWindowPeer> xParent(aEvent.SourceWindow, css::uno::UNO_QUERY); + xPopupMenu->execute(xParent, css::awt::Rectangle(aPos.X(), aPos.Y(), 1, 1), css::awt::PopupMenuDirection::EXECUTE_DOWN); + } + } + + css::uno::Reference< css::lang::XComponent > xComponent( xPopupController, css::uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); +} + +/** With this method the SfxDispatcher can be locked and released. A locked + SfxDispatcher does not perform <SfxRequest>s and does no longer provide + status information. It behaves as if all the slots were disabled. +*/ +void SfxDispatcher::Lock( bool bLock ) +{ + SfxBindings* pBindings = GetBindings(); + if ( !bLock && xImp->bLocked && xImp->bInvalidateOnUnlock ) + { + if ( pBindings ) + pBindings->InvalidateAll(true); + xImp->bInvalidateOnUnlock = false; + } + else if ( pBindings ) + pBindings->InvalidateAll(false); + xImp->bLocked = bLock; + if ( !bLock ) + { + for(size_t i = 0; i < xImp->aReqArr.size(); ++i) + xImp->xPoster->Post(std::move(xImp->aReqArr[i])); + xImp->aReqArr.clear(); + } +} + +ToolbarId SfxDispatcher::GetObjectBarId( sal_uInt16 nPos ) const +{ + return xImp->aObjBars[nPos].eId; +} + +void SfxDispatcher::HideUI( bool bHide ) +{ + bool bWasHidden = xImp->bNoUI; + xImp->bNoUI = bHide; + if ( xImp->pFrame ) + { + SfxViewFrame* pTop = xImp->pFrame->GetTopViewFrame(); + if ( pTop && pTop->GetBindings().GetDispatcher() == this ) + { + SfxFrame& rFrame = pTop->GetFrame(); + if ( rFrame.IsMenuBarOn_Impl() ) + { + css::uno::Reference < css::beans::XPropertySet > xPropSet( rFrame.GetFrameInterface(), css::uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager; + css::uno::Any aValue = xPropSet->getPropertyValue("LayoutManager"); + aValue >>= xLayoutManager; + if ( xLayoutManager.is() ) + xLayoutManager->setVisible( !bHide ); + } + } + } + } + + if ( bHide != bWasHidden ) + Update_Impl( true ); +} + +void SfxDispatcher::SetReadOnly_Impl( bool bOn ) +{ + xImp->bReadOnly = bOn; +} + +bool SfxDispatcher::GetReadOnly_Impl() const +{ + return xImp->bReadOnly; +} + +/** With 'bOn' the Dispatcher is quasi dead and transfers everything to the + Parent-Dispatcher. +*/ +void SfxDispatcher::SetQuietMode_Impl( bool bOn ) +{ + xImp->bQuiet = bOn; + SfxBindings* pBindings = GetBindings(); + if ( pBindings ) + pBindings->InvalidateAll(true); +} + +SfxItemState SfxDispatcher::QueryState( sal_uInt16 nSlot, const SfxPoolItem* &rpState ) +{ + SfxShell *pShell = nullptr; + const SfxSlot *pSlot = nullptr; + if ( GetShellAndSlot_Impl( nSlot, &pShell, &pSlot, false, true ) ) + { + rpState = pShell->GetSlotState(nSlot); + if ( !rpState ) + return SfxItemState::DISABLED; + else + return SfxItemState::DEFAULT; + } + + return SfxItemState::DISABLED; +} + +SfxItemState SfxDispatcher::QueryState( sal_uInt16 nSID, css::uno::Any& rAny ) +{ + SfxShell *pShell = nullptr; + const SfxSlot *pSlot = nullptr; + if ( GetShellAndSlot_Impl( nSID, &pShell, &pSlot, false, true ) ) + { + const SfxPoolItem* pItem = pShell->GetSlotState( nSID ); + if ( !pItem ) + return SfxItemState::DISABLED; + else + { + css::uno::Any aState; + if ( !pItem->IsVoidItem() ) + { + sal_uInt16 nSubId( 0 ); + SfxItemPool& rPool = pShell->GetPool(); + sal_uInt16 nWhich = rPool.GetWhich( nSID ); + if ( rPool.GetMetric( nWhich ) == MapUnit::MapTwip ) + nSubId |= CONVERT_TWIPS; + pItem->QueryValue( aState, static_cast<sal_uInt8>(nSubId) ); + } + rAny = aState; + + return SfxItemState::DEFAULT; + } + } + + return SfxItemState::DISABLED; +} + +bool SfxDispatcher::IsReadOnlyShell_Impl( sal_uInt16 nShell ) const +{ + sal_uInt16 nShellCount = xImp->aStack.size(); + if ( nShell < nShellCount ) + { + SfxShell* pShell = *( xImp->aStack.rbegin() + nShell ); + if( dynamic_cast< const SfxModule *>( pShell ) != nullptr || dynamic_cast< const SfxApplication *>( pShell ) != nullptr || dynamic_cast< const SfxViewFrame *>( pShell ) != nullptr ) + return false; + else + return xImp->bReadOnly; + } + return true; +} + +void SfxDispatcher::RemoveShell_Impl( SfxShell& rShell ) +{ + Flush(); + + sal_uInt16 nCount = xImp->aStack.size(); + for ( sal_uInt16 n=0; n<nCount; ++n ) + { + if ( xImp->aStack[n] == &rShell ) + { + xImp->aStack.erase( xImp->aStack.begin() + n ); + rShell.SetDisableFlags( SfxDisableFlags::NONE ); + rShell.DoDeactivate_Impl(xImp->pFrame, true); + break; + } + } + + if ( !SfxGetpApp()->IsDowning() ) + { + xImp->bUpdated = false; + InvalidateBindings_Impl(true); + } +} + +void SfxDispatcher::InvalidateBindings_Impl( bool bModify ) +{ + // App-Dispatcher? + if ( IsAppDispatcher() ) + { + for ( SfxViewFrame *pFrame = SfxViewFrame::GetFirst(); + pFrame; + pFrame = SfxViewFrame::GetNext( *pFrame ) ) + pFrame->GetBindings().InvalidateAll(bModify); + } + else + { + SfxDispatcher *pDisp = GetBindings()->GetDispatcher_Impl(); + if ( pDisp == this ) + { + GetBindings()->InvalidateAll( bModify ); + } + } +} + +bool SfxDispatcher::IsUpdated_Impl() const +{ + return xImp->bUpdated; +} + +void SfxDispatcher::SetDisableFlags( SfxDisableFlags nFlags ) +{ + xImp->nDisableFlags = nFlags; + for ( SfxShellStack_Impl::reverse_iterator it = xImp->aStack.rbegin(); it != xImp->aStack.rend(); ++it ) + (*it)->SetDisableFlags( nFlags ); +} + +SfxDisableFlags SfxDispatcher::GetDisableFlags() const +{ + return xImp->nDisableFlags; +} + +SfxModule* SfxDispatcher::GetModule() const +{ + for ( sal_uInt16 nShell = 0;; ++nShell ) + { + SfxShell *pSh = GetShell(nShell); + if ( pSh == nullptr ) + return nullptr; + if ( auto pModule = dynamic_cast<SfxModule *>( pSh ) ) + return pModule; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/emojicontrol.cxx b/sfx2/source/control/emojicontrol.cxx new file mode 100644 index 000000000..8b3c20607 --- /dev/null +++ b/sfx2/source/control/emojicontrol.cxx @@ -0,0 +1,156 @@ +/* -*- 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 <emojicontrol.hxx> +#include <emojipopup.hxx> +#include <emojiview.hxx> +#include <sfx2/thumbnailviewitem.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/dispatchcommand.hxx> +#include <officecfg/Office/Common.hxx> + +constexpr OStringLiteral FILTER_PEOPLE = "people"; +constexpr OStringLiteral FILTER_NATURE = "nature"; +constexpr OStringLiteral FILTER_FOOD = "food"; +constexpr OStringLiteral FILTER_ACTIVITY = "activity"; +constexpr OStringLiteral FILTER_TRAVEL = "travel"; +constexpr OStringLiteral FILTER_OBJECTS = "objects"; +constexpr OStringLiteral FILTER_SYMBOLS = "symbols"; +constexpr OStringLiteral FILTER_FLAGS = "flags"; +constexpr OStringLiteral FILTER_UNICODE9 = "unicode9"; + +using namespace com::sun::star; + +SfxEmojiControl::SfxEmojiControl(const EmojiPopup* pControl, weld::Widget* pParent) + : WeldToolbarPopup(pControl->getFrameInterface(), pParent, "sfx/ui/emojicontrol.ui", "emojictrl") + , mxPeopleBtn(m_xBuilder->weld_toggle_button(FILTER_PEOPLE)) + , mxNatureBtn(m_xBuilder->weld_toggle_button(FILTER_NATURE)) + , mxFoodBtn(m_xBuilder->weld_toggle_button(FILTER_FOOD)) + , mxActivityBtn(m_xBuilder->weld_toggle_button(FILTER_ACTIVITY)) + , mxTravelBtn(m_xBuilder->weld_toggle_button(FILTER_TRAVEL)) + , mxObjectsBtn(m_xBuilder->weld_toggle_button(FILTER_OBJECTS)) + , mxSymbolsBtn(m_xBuilder->weld_toggle_button(FILTER_SYMBOLS)) + , mxFlagsBtn(m_xBuilder->weld_toggle_button(FILTER_FLAGS)) + , mxUnicode9Btn(m_xBuilder->weld_toggle_button(FILTER_UNICODE9)) + , mxEmojiView(new EmojiView(m_xBuilder->weld_scrolled_window("emoji_win", true))) + , mxEmojiWeld(new weld::CustomWeld(*m_xBuilder, "emoji_view", *mxEmojiView)) +{ + ConvertLabelToUnicode(*mxPeopleBtn); + ConvertLabelToUnicode(*mxNatureBtn); + ConvertLabelToUnicode(*mxFoodBtn); + ConvertLabelToUnicode(*mxActivityBtn); + ConvertLabelToUnicode(*mxTravelBtn); + ConvertLabelToUnicode(*mxObjectsBtn); + ConvertLabelToUnicode(*mxSymbolsBtn); + ConvertLabelToUnicode(*mxFlagsBtn); + ConvertLabelToUnicode(*mxUnicode9Btn); + + mxPeopleBtn->connect_toggled(LINK(this, SfxEmojiControl, ActivatePageHdl)); + mxNatureBtn->connect_toggled(LINK(this, SfxEmojiControl, ActivatePageHdl)); + mxFoodBtn->connect_toggled(LINK(this, SfxEmojiControl, ActivatePageHdl)); + mxActivityBtn->connect_toggled(LINK(this, SfxEmojiControl, ActivatePageHdl)); + mxTravelBtn->connect_toggled(LINK(this, SfxEmojiControl, ActivatePageHdl)); + mxObjectsBtn->connect_toggled(LINK(this, SfxEmojiControl, ActivatePageHdl)); + mxSymbolsBtn->connect_toggled(LINK(this, SfxEmojiControl, ActivatePageHdl)); + mxFlagsBtn->connect_toggled(LINK(this, SfxEmojiControl, ActivatePageHdl)); + mxUnicode9Btn->connect_toggled(LINK(this, SfxEmojiControl, ActivatePageHdl)); + + mxEmojiView->setItemMaxTextLength(ITEM_MAX_TEXT_LENGTH); + mxEmojiView->setItemDimensions(ITEM_MAX_WIDTH, 0, ITEM_MAX_HEIGHT, ITEM_PADDING); + + mxEmojiView->Populate(); + ActivatePageHdl(*mxPeopleBtn); + + mxEmojiView->setInsertEmojiHdl(LINK(this, SfxEmojiControl, InsertHdl)); + mxEmojiView->ShowTooltips(true); +} + +void SfxEmojiControl::GrabFocus() +{ + mxEmojiView->GrabFocus(); +} + +SfxEmojiControl::~SfxEmojiControl() +{ +} + +void SfxEmojiControl::ConvertLabelToUnicode(weld::ToggleButton& rBtn) +{ + OUString sLabel = rBtn.get_label(); + sal_uInt32 nCodePoint = sLabel.toUInt32(16); + const OUString sHexText(&nCodePoint, 1); + rBtn.set_label(sHexText); +} + +FILTER_CATEGORY SfxEmojiControl::getFilter(const weld::Toggleable& rCurPageId) const +{ + if (&rCurPageId == mxPeopleBtn.get()) + return FILTER_CATEGORY::PEOPLE; + else if (&rCurPageId == mxNatureBtn.get()) + return FILTER_CATEGORY::NATURE; + else if (&rCurPageId == mxFoodBtn.get()) + return FILTER_CATEGORY::FOOD; + else if (&rCurPageId == mxActivityBtn.get()) + return FILTER_CATEGORY::ACTIVITY; + else if (&rCurPageId == mxTravelBtn.get()) + return FILTER_CATEGORY::TRAVEL; + else if (&rCurPageId == mxObjectsBtn.get()) + return FILTER_CATEGORY::OBJECTS; + else if (&rCurPageId == mxSymbolsBtn.get()) + return FILTER_CATEGORY::SYMBOLS; + else if (&rCurPageId == mxFlagsBtn.get()) + return FILTER_CATEGORY::FLAGS; + else if (&rCurPageId == mxUnicode9Btn.get()) + return FILTER_CATEGORY::UNICODE9; + + return FILTER_CATEGORY::PEOPLE; +} + +IMPL_LINK(SfxEmojiControl, ActivatePageHdl, weld::Toggleable&, rButton, void) +{ + mxPeopleBtn->set_active(&rButton == mxPeopleBtn.get()); + mxNatureBtn->set_active(&rButton == mxNatureBtn.get()); + mxFoodBtn->set_active(&rButton == mxFoodBtn.get()); + mxActivityBtn->set_active(&rButton == mxActivityBtn.get()); + mxTravelBtn->set_active(&rButton == mxTravelBtn.get()); + mxObjectsBtn->set_active(&rButton == mxObjectsBtn.get()); + mxSymbolsBtn->set_active(&rButton == mxSymbolsBtn.get()); + mxFlagsBtn->set_active(&rButton == mxFlagsBtn.get()); + mxUnicode9Btn->set_active(&rButton == mxUnicode9Btn.get()); + + mxEmojiView->filterItems(ViewFilter_Category(getFilter(rButton))); +} + +IMPL_STATIC_LINK(SfxEmojiControl, InsertHdl, ThumbnailViewItem*, pItem, void) +{ + const OUString& sHexText = pItem->getTitle(); + sal_uInt32 cEmojiChar = sHexText.toUInt32(16); + + OUString sFontName(officecfg::Office::Common::Misc::EmojiFont::get()); + + uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({ + { "Symbols", uno::Any(OUString(&cEmojiChar, 1)) }, + // add font settings here + { "FontName", uno::Any(sFontName) } + })); + + comphelper::dispatchCommand(".uno:InsertSymbol", aArgs); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/emojipopup.cxx b/sfx2/source/control/emojipopup.cxx new file mode 100644 index 000000000..237c21418 --- /dev/null +++ b/sfx2/source/control/emojipopup.cxx @@ -0,0 +1,73 @@ +/* -*- 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 <emojipopup.hxx> +#include <emojicontrol.hxx> +#include <vcl/toolbox.hxx> + +EmojiPopup::EmojiPopup(const css::uno::Reference<css::uno::XComponentContext>& rContext) + : PopupWindowController(rContext, nullptr, OUString()) +{ +} + +void EmojiPopup::initialize(const css::uno::Sequence<css::uno::Any>& rArguments) +{ + PopupWindowController::initialize(rArguments); + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if (getToolboxId(nId, &pToolBox)) + pToolBox->SetItemBits(nId, ToolBoxItemBits::DROPDOWNONLY | pToolBox->GetItemBits(nId)); +} + +EmojiPopup::~EmojiPopup() {} + +std::unique_ptr<WeldToolbarPopup> EmojiPopup::weldPopupWindow() +{ + return std::make_unique<SfxEmojiControl>(this, m_pToolbar); +} + +VclPtr<vcl::Window> EmojiPopup::createVclPopupWindow(vcl::Window* pParent) +{ + mxInterimPopover = VclPtr<InterimToolbarPopup>::Create( + getFrameInterface(), pParent, + std::make_unique<SfxEmojiControl>(this, pParent->GetFrameWeld())); + + mxInterimPopover->Show(); + + return mxInterimPopover; +} + +OUString EmojiPopup::getImplementationName() +{ + return "com.sun.star.comp.sfx2.InsertEmojiToolBoxControl"; +} + +css::uno::Sequence<OUString> EmojiPopup::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ToolbarController" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_sfx2_InsertEmojiToolBoxControl_get_implementation( + css::uno::XComponentContext* rContext, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new EmojiPopup(rContext)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/emojiview.cxx b/sfx2/source/control/emojiview.cxx new file mode 100644 index 000000000..6e5bd97b8 --- /dev/null +++ b/sfx2/source/control/emojiview.cxx @@ -0,0 +1,224 @@ +/* -*- 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/. + */ + +#include <osl/file.hxx> +#include <emojiview.hxx> +#include <emojiviewitem.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <config_folders.h> +#include <officecfg/Office/Common.hxx> +#include <comphelper/processfactory.hxx> +#include <vcl/event.hxx> +#include <vcl/weldutils.hxx> +#include <o3tl/string_view.hxx> + +#include <orcus/json_document_tree.hpp> +#include <orcus/config.hpp> +#include <string> +#include <string_view> +#include <fstream> + +using namespace ::com::sun::star; + +bool ViewFilter_Category::isFilteredCategory(FILTER_CATEGORY filter, std::u16string_view rCategory) +{ + bool bRet = true; + + if (filter == FILTER_CATEGORY::PEOPLE) + bRet = o3tl::starts_with(rCategory, u"people"); + else if (filter == FILTER_CATEGORY::NATURE) + bRet = o3tl::starts_with(rCategory, u"nature"); + else if (filter == FILTER_CATEGORY::FOOD) + bRet = o3tl::starts_with(rCategory, u"food"); + else if (filter == FILTER_CATEGORY::ACTIVITY) + bRet = o3tl::starts_with(rCategory, u"activity"); + else if (filter == FILTER_CATEGORY::TRAVEL) + bRet = o3tl::starts_with(rCategory, u"travel"); + else if (filter == FILTER_CATEGORY::OBJECTS) + bRet = o3tl::starts_with(rCategory, u"objects"); + else if (filter == FILTER_CATEGORY::SYMBOLS) + bRet = o3tl::starts_with(rCategory, u"symbols"); + else if (filter == FILTER_CATEGORY::FLAGS) + bRet = o3tl::starts_with(rCategory, u"flags"); + else if (filter == FILTER_CATEGORY::UNICODE9) + bRet = o3tl::starts_with(rCategory, u"unicode9"); + + return bRet; +} + +bool ViewFilter_Category::operator () (const ThumbnailViewItem *pItem) +{ + const EmojiViewItem *pViewItem = dynamic_cast<const EmojiViewItem*>(pItem); + if (pViewItem) + return isFilteredCategory(mCategory, pViewItem->getCategory()); + + return true; +} + +EmojiView::EmojiView(std::unique_ptr<weld::ScrolledWindow> xWindow) + : ThumbnailView(std::move(xWindow), nullptr) +{ + // locate json data file + OUString aURL("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/emojiconfig/emoji.json"); + rtl::Bootstrap::expandMacros(aURL); + + OUString aPath; + osl::FileBase::getSystemPathFromFileURL(aURL, aPath); + std::string strPath = OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(); + + std::ifstream file(strPath); + if(!file.is_open()) + return; + + msJSONData = std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); + if(msJSONData.empty()) + return; +} + +void EmojiView::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + ThumbnailView::SetDrawingArea(pDrawingArea); + + OUString sFontName(officecfg::Office::Common::Misc::EmojiFont::get()); + vcl::Font aFont = pDrawingArea->get_font(); + aFont.SetFamilyName(sFontName); + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + weld::SetPointFont(rDevice, aFont); + + mpItemAttrs->aFontSize.setX(ITEM_MAX_WIDTH - 2*ITEM_PADDING); + mpItemAttrs->aFontSize.setY(ITEM_MAX_HEIGHT - 2*ITEM_PADDING); +} + +EmojiView::~EmojiView() +{ +} + +void EmojiView::Populate() +{ + if (msJSONData.empty()) + { + SAL_WARN("sfx", "Emoji config data is empty"); + return; + } + + // Populate view using the orcus json parser + using node = orcus::json::node; + + // default json config + orcus::json_config config; + + orcus::json::document_tree aEmojiInfo; + + // Load JSON string into a document tree. + aEmojiInfo.load(msJSONData, config); + + node root = aEmojiInfo.get_document_root(); + std::vector<std::string_view> keys = root.keys(); + + for (auto const& key : keys) + { + node value = root.child(key); + + if(value.type() == orcus::json::node_t::object) + { + // iterate each element to get the keys + std::vector<std::string_view> aEmojiParams = value.keys(); + OUString sTitle, sCategory, sName; + bool bDuplicate = false; + + for (auto const& emojiParam : aEmojiParams) + { + node prop = value.child(emojiParam); + + // get values of parameters in AppendItem() function + if(emojiParam == "unicode") + { + sTitle = OStringToOUString(prop.string_value(), RTL_TEXTENCODING_UTF8); + } + else if(emojiParam == "category") + { + sCategory = OStringToOUString(prop.string_value(), RTL_TEXTENCODING_UTF8); + } + else if(emojiParam == "name") + { + sName = OStringToOUString(prop.string_value(), RTL_TEXTENCODING_UTF8); + } + else if(emojiParam == "duplicate") + { + bDuplicate = true; + } + } + + // Don't append if a duplicate emoji + if(!bDuplicate) + { + AppendItem(sTitle, sCategory, sName); + } + } + } +} + +bool EmojiView::MouseButtonDown( const MouseEvent& rMEvt ) +{ + GrabFocus(); + + if (rMEvt.IsLeft()) + { + size_t nPos = ImplGetItem(rMEvt.GetPosPixel()); + ThumbnailViewItem* pItem = ImplGetItem(nPos); + + if(pItem) + maInsertEmojiHdl.Call(pItem); + } + + return true; +} + +bool EmojiView::KeyInput( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if(aKeyCode == ( KEY_MOD1 | KEY_A ) ) + { + for (ThumbnailViewItem* pItem : mFilteredItemList) + { + if (!pItem->isSelected()) + { + pItem->setSelection(true); + } + } + + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + return true; + } + + return ThumbnailView::KeyInput(rKEvt); +} + +void EmojiView::setInsertEmojiHdl(const Link<ThumbnailViewItem*, void> &rLink) +{ + maInsertEmojiHdl = rLink; +} + +void EmojiView::AppendItem(const OUString &rTitle, const OUString &rCategory, const OUString &rName) +{ + std::unique_ptr<EmojiViewItem> pItem(new EmojiViewItem(*this, getNextItemId())); + + pItem->maTitle = rTitle; + pItem->setCategory(rCategory); + pItem->setHelpText(rName); + + ThumbnailView::AppendItem(std::move(pItem)); + + CalculateItemPositions(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/emojiviewitem.cxx b/sfx2/source/control/emojiviewitem.cxx new file mode 100644 index 000000000..99a727315 --- /dev/null +++ b/sfx2/source/control/emojiviewitem.cxx @@ -0,0 +1,84 @@ +/* -*- 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/. + */ + +#include <emojiviewitem.hxx> + +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/PolyPolygonSelectionPrimitive2D.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <com/sun/star/lang/Locale.hpp> +#include <tools/poly.hxx> + +using namespace basegfx; +using namespace basegfx::utils; +using namespace drawinglayer::attribute; +using namespace drawinglayer::primitive2d; + +EmojiViewItem::EmojiViewItem(ThumbnailView& rView, sal_uInt16 nId) + : ThumbnailViewItem(rView, nId) +{ +} + +EmojiViewItem::~EmojiViewItem () +{ +} + +void EmojiViewItem::calculateItemsPosition (const tools::Long /*nThumbnailHeight*/, + const tools::Long /*nPadding*/, sal_uInt32 nMaxTextLength, + const ThumbnailItemAttributes *pAttrs) +{ + drawinglayer::primitive2d::TextLayouterDevice aTextDev; + aTextDev.setFontAttribute(pAttrs->aFontAttr, + pAttrs->aFontSize.getX(), pAttrs->aFontSize.getY(), + css::lang::Locale() ); + + Size aRectSize = maDrawArea.GetSize(); + Point aPos = maDrawArea.TopCenter(); + + // Calculate text position + aPos.Move(-aTextDev.getTextWidth(maTitle, 0, nMaxTextLength) / 2, + (aRectSize.Height() - aTextDev.getTextHeight()) / 3); + maTextPos = aPos; +} + + +void EmojiViewItem::Paint(drawinglayer::processor2d::BaseProcessor2D *pProcessor, + const ThumbnailItemAttributes *pAttrs) +{ + BColor aFillColor = pAttrs->aFillColor; + + drawinglayer::primitive2d::Primitive2DContainer aSeq(2); + double fTransparence = 0.0; + + // Draw background + if( mbSelected && mbHover) + aFillColor = pAttrs->aSelectHighlightColor; + else if (mbSelected || mbHover) + aFillColor = pAttrs->aHighlightColor; + + if (mbHover) + fTransparence = pAttrs->fHighlightTransparence; + + aSeq[0] = drawinglayer::primitive2d::Primitive2DReference( + new PolyPolygonSelectionPrimitive2D( B2DPolyPolygon(::tools::Polygon(maDrawArea,5,5).getB2DPolygon()), + aFillColor, + fTransparence, + 0.0, + true)); + + sal_uInt32 nCodePoint = maTitle.toUInt32(16); + const OUString sHexText(&nCodePoint, 1); + + addTextPrimitives(sHexText, pAttrs, maTextPos, aSeq); + + pProcessor->process(aSeq); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/itemdel.cxx b/sfx2/source/control/itemdel.cxx new file mode 100644 index 000000000..2b8e7db73 --- /dev/null +++ b/sfx2/source/control/itemdel.cxx @@ -0,0 +1,77 @@ +/* -*- 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 <itemdel.hxx> +#include <svl/poolitem.hxx> +#include <vcl/idle.hxx> + +#include <tools/debug.hxx> + +class SfxItemDisruptor_Impl +{ + std::unique_ptr<SfxPoolItem> pItem; + Idle m_Idle; + +private: + DECL_LINK(Delete, Timer*, void); + +public: + explicit SfxItemDisruptor_Impl(std::unique_ptr<SfxPoolItem> pItemToDesrupt); + void LaunchDeleteOnIdle(); + ~SfxItemDisruptor_Impl(); + SfxItemDisruptor_Impl(const SfxItemDisruptor_Impl&) = delete; + SfxItemDisruptor_Impl& operator=(const SfxItemDisruptor_Impl&) = delete; +}; + +SfxItemDisruptor_Impl::SfxItemDisruptor_Impl(std::unique_ptr<SfxPoolItem> pItemToDisrupt) + : pItem(std::move(pItemToDisrupt)) + , m_Idle("sfx::SfxItemDisruptor_Impl m_Idle") +{ + m_Idle.SetInvokeHandler(LINK(this, SfxItemDisruptor_Impl, Delete)); + m_Idle.SetPriority(TaskPriority::DEFAULT_IDLE); + + DBG_ASSERT(0 == pItem->GetRefCount(), "disrupting pooled item"); + pItem->SetKind(SfxItemKind::DeleteOnIdle); +} + +void SfxItemDisruptor_Impl::LaunchDeleteOnIdle() { m_Idle.Start(); } + +SfxItemDisruptor_Impl::~SfxItemDisruptor_Impl() +{ + m_Idle.Stop(); + + // reset RefCount (was set to SFX_ITEMS_SPECIAL before!) + pItem->SetRefCount(0); + + pItem.reset(); +} + +IMPL_LINK_NOARG(SfxItemDisruptor_Impl, Delete, Timer*, void) { delete this; } + +void DeleteItemOnIdle(std::unique_ptr<SfxPoolItem> pItem) +{ + DBG_ASSERT(0 == pItem->GetRefCount(), "deleting item in use"); + SfxItemDisruptor_Impl* pDesruptor = new SfxItemDisruptor_Impl(std::move(pItem)); + pDesruptor->LaunchDeleteOnIdle(); + // coverity[leaked_storage] - pDesruptor takes care of its own destruction at idle time +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/listview.cxx b/sfx2/source/control/listview.cxx new file mode 100644 index 000000000..a53e85d71 --- /dev/null +++ b/sfx2/source/control/listview.cxx @@ -0,0 +1,443 @@ +/* -*- 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/. + */ + +#include <sfx2/listview.hxx> + +#include <sfx2/sfxresid.hxx> +#include <tools/urlobj.hxx> +#include <tools/datetime.hxx> +#include <sfx2/strings.hrc> +#include <osl/file.hxx> +#include <osl/time.h> +#include <comphelper/fileurl.hxx> + +#include <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <unotools/localedatawrapper.hxx> +#include <unotools/collatorwrapper.hxx> +#include <unotools/syslocale.hxx> +#include <unotools/intlwrapper.hxx> +#include <tools/wintypes.hxx> + +#include <bitmaps.hlst> +#include <rtl/math.hxx> + +#include <sfx2/templatelocalview.hxx> + +#define COLUMN_IMG_ISDEFAULT 0 +#define COLUMN_NAME 1 +#define COLUMN_CATEGORY 2 +#define COLUMN_APPLICATION 3 +#define COLUMN_MODIFIED 4 +#define COLUMN_SIZE 5 +#define NUMBER_OF_COLUMNS 6 + +static sal_uInt64 getFileSize(const OUString& rURL); +static sal_uInt32 getFileModifyTime(const OUString& rURL); +static OUString getDisplayFileSize(const OUString& rURL); +static OUString getDisplayFileModifyTime(const OUString& rURL); +static OUString getApplication(std::u16string_view rURL); + +ListView::ListView(std::unique_ptr<weld::TreeView> xTreeView) + : mxTreeView(std::move(xTreeView)) + , mnSortColumn(-2) +{ + auto nDigitWidth = mxTreeView->get_approximate_digit_width(); + std::vector<int> aWidths{ + static_cast<int>(nDigitWidth * 5), /* Icon Column */ + static_cast<int>(nDigitWidth * 24), /* Name Column */ + static_cast<int>(nDigitWidth * 22), /* Category Column */ + static_cast<int>(nDigitWidth * 15), /* Application Column */ + static_cast<int>(nDigitWidth * 18) /* Modify Column */ + }; + + mxTreeView->set_column_fixed_widths(aWidths); + mxTreeView->set_selection_mode(SelectionMode::Multiple); + mxTreeView->connect_query_tooltip(LINK(this, ListView, QueryTooltipHdl)); +} +ListView::~ListView() {} + +void ListView::AppendItem(const OUString& rId, const OUString& rTitle, const OUString& rSubtitle, + const OUString& rPath, bool bDefault) +{ + INetURLObject aUrl(rPath, INetProtocol::File); + OUString sPath = aUrl.getFSysPath(FSysStyle::Detect); + + std::unique_ptr<ListViewItem> pItem(new ListViewItem); + pItem->maId = rId; + pItem->maTitle = rTitle; + pItem->maSubtitle = rSubtitle; + pItem->maApplication = getApplication(rPath); + pItem->maPath = rPath; + pItem->mbDefault = bDefault; + pItem->mnModify = getFileModifyTime(rPath); + pItem->mnSize = getFileSize(rPath); + pItem->maDisplayModify = getDisplayFileModifyTime(rPath); + pItem->maDisplaySize = getDisplayFileSize(rPath); + pItem->maDisplayPath = sPath; + + OUString sImage(""); + if (pItem->mbDefault) + sImage = BMP_DEFAULT; + + AppendRow(sImage, pItem->maTitle, pItem->maSubtitle, pItem->maApplication, + pItem->maDisplayModify, pItem->maDisplaySize, pItem->maId); + + mListViewItems.push_back(std::move(pItem)); +} + +void ListView::AppendRow(const OUString& rImage, const OUString& rTitle, const OUString& rSubtitle, + const OUString& rApplication, const OUString& rModify, + const OUString& rSize, const OUString& rId) +{ + std::unique_ptr<weld::TreeIter> xIter(mxTreeView->make_iterator()); + mxTreeView->append(xIter.get()); + mxTreeView->set_image(*xIter, rImage, COLUMN_IMG_ISDEFAULT); + mxTreeView->set_text(*xIter, rTitle, COLUMN_NAME); + mxTreeView->set_text(*xIter, rSubtitle, COLUMN_CATEGORY); + mxTreeView->set_text(*xIter, rApplication, COLUMN_APPLICATION); + mxTreeView->set_text(*xIter, rModify, COLUMN_MODIFIED); + mxTreeView->set_text(*xIter, rSize, COLUMN_SIZE); + mxTreeView->set_id(*xIter, rId); +} + +void ListView::UpdateRow(int nIndex, const OUString& rImage, const OUString& rTitle, + const OUString& rSubtitle, const OUString& rApplication, + const OUString& rModify, const OUString& rSize, const OUString& rId) +{ + mxTreeView->set_image(nIndex, rImage, COLUMN_IMG_ISDEFAULT); + mxTreeView->set_text(nIndex, rTitle, COLUMN_NAME); + mxTreeView->set_text(nIndex, rSubtitle, COLUMN_CATEGORY); + mxTreeView->set_text(nIndex, rApplication, COLUMN_APPLICATION); + mxTreeView->set_text(nIndex, rModify, COLUMN_MODIFIED); + mxTreeView->set_text(nIndex, rSize, COLUMN_SIZE); + mxTreeView->set_id(nIndex, rId); +} + +void ListView::ReloadRows() +{ + OUString sCursorId = get_id(get_cursor_index()); + mxTreeView->clear(); + for (const auto& pItem : mListViewItems) + { + OUString sImage(""); + if (pItem->mbDefault) + sImage = BMP_DEFAULT; + AppendRow(sImage, pItem->maTitle, pItem->maSubtitle, pItem->maApplication, + pItem->maDisplayModify, pItem->maDisplaySize, pItem->maId); + } + unselect_all(); + if (!sCursorId.isEmpty()) + { + select_id(sCursorId); + set_cursor(get_selected_index()); + } +} + +bool ListView::UpdateRows() +{ + if (static_cast<int>(mListViewItems.size()) != mxTreeView->n_children()) + return false; + OUString sCursorId = get_id(get_cursor_index()); + int nIndex = 0; + for (const auto& pItem : mListViewItems) + { + OUString sImage(""); + if (pItem->mbDefault) + sImage = BMP_DEFAULT; + UpdateRow(nIndex, sImage, pItem->maTitle, pItem->maSubtitle, pItem->maApplication, + pItem->maDisplayModify, pItem->maDisplaySize, pItem->maId); + ++nIndex; + } + unselect_all(); + if (!sCursorId.isEmpty()) + { + select_id(sCursorId); + set_cursor(get_selected_index()); + } + return true; +} + +IMPL_LINK(ListView, ColumnClickedHdl, const int, col, void) +{ + if (col <= 0 || col > NUMBER_OF_COLUMNS) + return; + + if (mnSortColumn >= 0 && mnSortColumn != col) + mxTreeView->set_sort_indicator(TriState::TRISTATE_INDET, mnSortColumn); + + mxTreeView->set_sort_indicator((mxTreeView->get_sort_indicator(col) == TriState::TRISTATE_TRUE + ? TriState::TRISTATE_FALSE + : TriState::TRISTATE_TRUE), + col); + sortColumn(col); +} + +void ListView::sortColumn(const int col) +{ + if (col <= 0 || col > NUMBER_OF_COLUMNS) + return; + + bool isAscending = mxTreeView->get_sort_indicator(col) != TriState::TRISTATE_FALSE; + + auto comp = [&](std::unique_ptr<ListViewItem> const& pItemA, + std::unique_ptr<ListViewItem> const& pItemB) { + sal_Int32 res = 0; + IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag()); + const CollatorWrapper* pCollatorWrapper = aIntlWrapper.getCollator(); + switch (col) + { + case COLUMN_NAME: + { + OUString sNameA = pItemA->maTitle; + OUString sNameB = pItemB->maTitle; + res = pCollatorWrapper->compareString(sNameA, sNameB); + } + break; + case COLUMN_CATEGORY: + { + OUString sCategoryA = pItemA->maSubtitle; + OUString sCategoryB = pItemB->maSubtitle; + res = pCollatorWrapper->compareString(sCategoryA, sCategoryB); + } + break; + case COLUMN_MODIFIED: + { + sal_uInt32 nModA, nModB; + nModA = pItemA->mnModify; + nModB = pItemB->mnModify; + + if (nModA < nModB) + res = -1; + else if (nModA > nModB) + res = 1; + } + break; + case COLUMN_SIZE: + { + sal_uInt64 nSizeA, nSizeB; + nSizeA = pItemA->mnSize; + nSizeB = pItemB->mnSize; + + if (nSizeA < nSizeB) + res = -1; + else if (nSizeA > nSizeB) + res = 1; + } + break; + case COLUMN_APPLICATION: + { + OUString sPathA = pItemA->maApplication; + OUString sPathB = pItemB->maApplication; + res = pCollatorWrapper->compareString(sPathA, sPathB); + } + break; + } + return isAscending ? (res > 0) : (res < 0); + }; + std::stable_sort(mListViewItems.begin(), mListViewItems.end(), comp); + + if (!UpdateRows()) + ReloadRows(); + mnSortColumn = col; +} + +void ListView::sort() { sortColumn(mnSortColumn); } + +void ListView::refreshDefaultColumn() +{ + for (const auto& pItem : mListViewItems) + { + bool bDefault = TemplateLocalView::IsDefaultTemplate(pItem->maPath); + if (pItem->mbDefault != bDefault) + { + pItem->mbDefault = bDefault; + OUString sImage(""); + if (bDefault) + sImage = BMP_DEFAULT; + mxTreeView->set_image(mxTreeView->find_id(pItem->maId), sImage, COLUMN_IMG_ISDEFAULT); + } + } +} + +void ListView::rename(const OUString& rId, const OUString& rTitle) +{ + mxTreeView->set_text(mxTreeView->find_id(rId), rTitle, COLUMN_NAME); + for (const auto& pItem : mListViewItems) + if (pItem->maId == rId) + { + pItem->maTitle = rTitle; + break; + } +} + +void ListView::clearListView() +{ + mxTreeView->clear(); + mListViewItems.clear(); +} + +IMPL_LINK(ListView, QueryTooltipHdl, const weld::TreeIter&, rIter, OUString) +{ + OUString sId = mxTreeView->get_id(rIter); + for (const auto& pItem : mListViewItems) + { + if (pItem->maId == sId) + return pItem->maDisplayPath; + } + return OUString(); +} + +sal_uInt16 ListView::get_nId(int pos) const +{ + return static_cast<sal_uInt16>(mxTreeView->get_id(pos).toInt32()); +} + +static sal_uInt32 getFileModifyTime(const OUString& rURL) +{ + sal_uInt32 nModify = 0; + if (!comphelper::isFileUrl(rURL)) + return nModify; + + osl::DirectoryItem aItem; + if (osl::DirectoryItem::get(rURL, aItem) != osl::DirectoryItem::E_None) + return nModify; + + osl::FileStatus aStatus(osl_FileStatus_Mask_ModifyTime); + if (aItem.getFileStatus(aStatus) != osl::DirectoryItem::E_None) + return nModify; + + TimeValue systemTimeValue = aStatus.getModifyTime(); + + nModify = systemTimeValue.Seconds; + return nModify; +} +static OUString getDisplayFileModifyTime(const OUString& rURL) +{ + if (!comphelper::isFileUrl(rURL)) + return OUString(); + + osl::DirectoryItem aItem; + if (osl::DirectoryItem::get(rURL, aItem) != osl::DirectoryItem::E_None) + return OUString(); + + osl::FileStatus aStatus(osl_FileStatus_Mask_ModifyTime); + if (aItem.getFileStatus(aStatus) != osl::DirectoryItem::E_None) + return OUString(); + + TimeValue systemTimeValue = aStatus.getModifyTime(); + if (systemTimeValue.Seconds == 0) + return OUString(); + TimeValue localTimeValue; + osl_getLocalTimeFromSystemTime(&systemTimeValue, &localTimeValue); + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleWrapper = aSysLocale.GetLocaleData(); + DateTime aDateTime = DateTime::CreateFromUnixTime(localTimeValue.Seconds); + OUString aDisplayDateTime + = rLocaleWrapper.getDate(aDateTime) + ", " + rLocaleWrapper.getTime(aDateTime, false); + return aDisplayDateTime; +} + +static OUString getDisplayFileSize(const OUString& rURL) +{ + if (!comphelper::isFileUrl(rURL)) + return OUString(); + + osl::DirectoryItem aItem; + if (osl::DirectoryItem::get(rURL, aItem) != osl::DirectoryItem::E_None) + return OUString(); + + osl::FileStatus aStatus(osl_FileStatus_Mask_FileSize); + if (aItem.getFileStatus(aStatus) != osl::DirectoryItem::E_None) + return OUString(); + + sal_uInt64 nSize = aStatus.getFileSize(); + double fSize(static_cast<double>(nSize)); + sal_uInt32 nDec; + + sal_uInt64 nMega = 1024 * 1024; + sal_uInt64 nGiga = nMega * 1024; + + OUString aUnitStr(' '); + + if (nSize < 10000) + { + aUnitStr += SvtResId(STR_SVT_BYTES); + nDec = 0; + } + else if (nSize < nMega) + { + fSize /= 1024; + aUnitStr += SvtResId(STR_SVT_KB); + nDec = 1; + } + else if (nSize < nGiga) + { + fSize /= nMega; + aUnitStr += SvtResId(STR_SVT_MB); + nDec = 2; + } + else + { + fSize /= nGiga; + aUnitStr += SvtResId(STR_SVT_GB); + nDec = 3; + } + + OUString aSizeStr( + ::rtl::math::doubleToUString(fSize, rtl_math_StringFormat_F, nDec, + SvtSysLocale().GetLocaleData().getNumDecimalSep()[0])); + aSizeStr += aUnitStr; + + return aSizeStr; +} + +static sal_uInt64 getFileSize(const OUString& rURL) +{ + sal_uInt64 nSize = 0; + if (!comphelper::isFileUrl(rURL)) + return nSize; + + osl::DirectoryItem aItem; + if (osl::DirectoryItem::get(rURL, aItem) != osl::DirectoryItem::E_None) + return nSize; + + osl::FileStatus aStatus(osl_FileStatus_Mask_FileSize); + if (aItem.getFileStatus(aStatus) != osl::DirectoryItem::E_None) + return nSize; + + nSize = aStatus.getFileSize(); + return nSize; +} + +static OUString getApplication(std::u16string_view rURL) +{ + INetURLObject aUrl(rURL); + OUString aExt = aUrl.getExtension(); + + if (aExt == "ott" || aExt == "stw" || aExt == "oth" || aExt == "dot" || aExt == "dotx") + { + return SfxResId(STR_DOCUMENT); + } + else if (aExt == "ots" || aExt == "stc" || aExt == "xlt" || aExt == "xltm" || aExt == "xltx") + { + return SfxResId(STR_SPREADSHEET); + } + else if (aExt == "otp" || aExt == "sti" || aExt == "pot" || aExt == "potm" || aExt == "potx") + { + return SfxResId(STR_PRESENTATION); + } + else if (aExt == "otg" || aExt == "std") + { + return SfxResId(STR_DRAWING); + } + return OUString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/minfitem.cxx b/sfx2/source/control/minfitem.cxx new file mode 100644 index 000000000..8d8f0a80a --- /dev/null +++ b/sfx2/source/control/minfitem.cxx @@ -0,0 +1,75 @@ +/* -*- 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 <sfx2/minfitem.hxx> +#include <sal/log.hxx> +#include <config_features.h> + +#if HAVE_FEATURE_SCRIPTING + +SfxPoolItem* SfxMacroInfoItem::CreateDefault() { SAL_WARN( "sfx", "No SfxMacroInfItem factory available"); return nullptr; } + + +SfxMacroInfoItem::SfxMacroInfoItem( + sal_uInt16 nWhichId, // Slot-ID + const BasicManager* pMgr, + const OUString &rLibName, + const OUString &rModuleName, + const OUString &rMethodName, + const OUString &rComment) : + SfxPoolItem(nWhichId), + pBasicManager(pMgr), + aLibName(rLibName), + aModuleName(rModuleName), + aMethodName(rMethodName), + aCommentText(rComment) +{ +} + +// op == + +bool SfxMacroInfoItem::operator==( const SfxPoolItem& rCmp) const +{ + const SfxMacroInfoItem rItem = static_cast<const SfxMacroInfoItem&>(rCmp); + return SfxPoolItem::operator==(rCmp) && + pBasicManager == rItem.pBasicManager && + aLibName == rItem.aLibName && + aModuleName == rItem.aModuleName && + aMethodName == rItem.aMethodName && + aCommentText == rItem.aCommentText; +} + +SfxMacroInfoItem* SfxMacroInfoItem::Clone( SfxItemPool *) const +{ + return new SfxMacroInfoItem(*this); +} + +OUString SfxMacroInfoItem::GetQualifiedName() const +{ + OUString aMacroName = aLibName + + "." + + aModuleName + + "." + + aMethodName; + return aMacroName; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/msg.cxx b/sfx2/source/control/msg.cxx new file mode 100644 index 000000000..c6ed821c1 --- /dev/null +++ b/sfx2/source/control/msg.cxx @@ -0,0 +1,56 @@ +/* -*- 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 <svl/itempool.hxx> +#include <sfx2/msg.hxx> + +#include <climits> + +SfxSlotKind SfxSlot::GetKind() const +{ + if( !nMasterSlotId && !nValue) + return SfxSlotKind::Standard; + if ( nMasterSlotId && fnExec==nullptr && fnState==nullptr ) + { + assert(false); + return SfxSlotKind::Standard; + } + else + return SfxSlotKind::Attribute; +} + + +sal_uInt16 SfxSlot::GetWhich( const SfxItemPool &rPool ) const +{ + if ( !nMasterSlotId || nMasterSlotId == USHRT_MAX ) + const_cast<SfxSlot*>(this) -> nMasterSlotId = rPool.GetWhich(nSlotId); + return nMasterSlotId; +} + +OString SfxSlot::GetCommand() const +{ + return OString::Concat(".uno:") + pUnoName; +} + +OUString SfxSlot::GetCommandString() const +{ + return OStringToOUString(GetCommand(), RTL_TEXTENCODING_UTF8); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/msgpool.cxx b/sfx2/source/control/msgpool.cxx new file mode 100644 index 000000000..f7a94203e --- /dev/null +++ b/sfx2/source/control/msgpool.cxx @@ -0,0 +1,326 @@ +/* -*- 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/log.hxx> +#include <osl/diagnose.h> + +// due to pSlotPool +#include <appdata.hxx> +#include <sfx2/msgpool.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/app.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/module.hxx> + +#include <sfx2/strings.hrc> + +SfxSlotPool::SfxSlotPool(SfxSlotPool *pParent) + : _pParentPool( pParent ) + , _nCurGroup(0) + , _nCurInterface(0) + , _nCurMsg(0) +{ +} + +SfxSlotPool::~SfxSlotPool() +{ + _pParentPool = nullptr; + // swap out _vInterfaces because ~SfxInterface() might call ReleaseInterface() + std::vector<SfxInterface*> tmpInterfaces; + tmpInterfaces.swap(_vInterfaces); + for ( SfxInterface *pIF : tmpInterfaces ) + delete pIF; +} + +namespace +{ + TranslateId getGidResId(SfxGroupId nId) + { + if (nId == SfxGroupId::Intern) + return STR_GID_INTERN; + else if (nId == SfxGroupId::Application) + return STR_GID_APPLICATION; + else if (nId == SfxGroupId::View) + return STR_GID_VIEW; + else if (nId == SfxGroupId::Document) + return STR_GID_DOCUMENT; + else if (nId == SfxGroupId::Edit) + return STR_GID_EDIT; + else if (nId == SfxGroupId::Macro) + return STR_GID_MACRO; + else if (nId == SfxGroupId::Options) + return STR_GID_OPTIONS; + else if (nId == SfxGroupId::Math) + return STR_GID_MATH; + else if (nId == SfxGroupId::Navigator) + return STR_GID_NAVIGATOR; + else if (nId == SfxGroupId::Insert) + return STR_GID_INSERT; + else if (nId == SfxGroupId::Format) + return STR_GID_FORMAT; + else if (nId == SfxGroupId::Template) + return STR_GID_TEMPLATE; + else if (nId == SfxGroupId::Text) + return STR_GID_TEXT; + else if (nId == SfxGroupId::Frame) + return STR_GID_FRAME; + else if (nId == SfxGroupId::Graphic) + return STR_GID_GRAPHIC; + else if (nId == SfxGroupId::Table) + return STR_GID_TABLE; + else if (nId == SfxGroupId::Enumeration) + return STR_GID_ENUMERATION; + else if (nId == SfxGroupId::Data) + return STR_GID_DATA; + else if (nId == SfxGroupId::Special) + return STR_GID_SPECIAL; + else if (nId == SfxGroupId::Image) + return STR_GID_IMAGE; + else if (nId == SfxGroupId::Chart) + return STR_GID_CHART; + else if (nId == SfxGroupId::Explorer) + return STR_GID_EXPLORER; + else if (nId == SfxGroupId::Connector) + return STR_GID_CONNECTOR; + else if (nId == SfxGroupId::Modify) + return STR_GID_MODIFY; + else if (nId == SfxGroupId::Drawing) + return STR_GID_DRAWING; + else if (nId == SfxGroupId::Controls) + return STR_GID_CONTROLS; + return {}; + } +} + +// registers the availability of the Interface of functions + +void SfxSlotPool::RegisterInterface( SfxInterface& rInterface ) +{ + // add to the list of SfxObjectInterface instances + _vInterfaces.push_back(&rInterface); + + // Stop at a (single) Null-slot (for syntactic reasons the interfaces + // always contain at least one slot) + if ( rInterface.Count() != 0 && !rInterface.pSlots[0].nSlotId ) + return; + + // possibly add Interface-id and group-ids of funcs to the list of groups + if ( _pParentPool ) + { + // The Groups in parent Slotpool are also known here + _vGroups.insert( _vGroups.end(), _pParentPool->_vGroups.begin(), _pParentPool->_vGroups.end() ); + } + + for ( size_t nFunc = 0; nFunc < rInterface.Count(); ++nFunc ) + { + SfxSlot &rDef = rInterface.pSlots[nFunc]; + if ( rDef.GetGroupId() != SfxGroupId::NONE && + std::find(_vGroups.begin(), _vGroups.end(), rDef.GetGroupId()) == _vGroups.end() ) + { + if (rDef.GetGroupId() == SfxGroupId::Intern) + _vGroups.insert(_vGroups.begin(), rDef.GetGroupId()); + else + _vGroups.push_back(rDef.GetGroupId()); + } + } +} + + +const std::type_info* SfxSlotPool::GetSlotType( sal_uInt16 nId ) const +{ + const SfxSlot* pSlot = GetSlot( nId ); + return pSlot ? pSlot->GetType()->Type() : nullptr; +} + + +// get the first SfxMessage for a special Id (e.g. for getting check-mode) + +const SfxSlot* SfxSlotPool::GetSlot( sal_uInt16 nId ) const +{ + // First, search their own interfaces + for (SfxInterface* _pInterface : _vInterfaces) + { + const SfxSlot *pDef = _pInterface->GetSlot(nId); + if ( pDef ) + return pDef; + } + + // Then try any of the possible existing parent + return _pParentPool ? _pParentPool->GetSlot( nId ) : nullptr; +} + + +// skips to the next group + +OUString SfxSlotPool::SeekGroup( sal_uInt16 nNo ) +{ + // if the group exists, use it + if ( nNo < _vGroups.size() ) + { + _nCurGroup = nNo; + if ( _pParentPool ) + { + // In most cases, the order of the IDs agree + sal_uInt16 nParentCount = _pParentPool->_vGroups.size(); + if ( nNo < nParentCount && _vGroups[nNo] == _pParentPool->_vGroups[nNo] ) + _pParentPool->_nCurGroup = nNo; + else + { + // Otherwise search. If the group is not found in the parent + // pool, _nCurGroup is set outside the valid range + sal_uInt16 i; + for ( i=1; i<nParentCount; i++ ) + if ( _vGroups[nNo] == _pParentPool->_vGroups[i] ) + break; + _pParentPool->_nCurGroup = i; + } + } + + TranslateId pResId = getGidResId(_vGroups[_nCurGroup]); + if (!pResId) + { + OSL_FAIL( "GroupId-Name not defined in SFX!" ); + return OUString(); + } + + return SfxResId(pResId); + } + + return OUString(); +} + + +sal_uInt16 SfxSlotPool::GetGroupCount() const +{ + return _vGroups.size(); +} + + +// internal search loop + +const SfxSlot* SfxSlotPool::SeekSlot( sal_uInt16 nStartInterface ) +{ + // The numbering starts at the interfaces of the parent pool + sal_uInt16 nFirstInterface = _pParentPool ? _pParentPool->_vInterfaces.size() : 0; + + // have reached the end of the Parent-Pools? + if ( nStartInterface < nFirstInterface && + _pParentPool->_nCurGroup >= _pParentPool->_vGroups.size() ) + nStartInterface = nFirstInterface; + + // Is the Interface still in the Parent-Pool? + if ( nStartInterface < nFirstInterface ) + { + SAL_WARN_IF(!_pParentPool, "sfx.control", "No parent pool!"); + _nCurInterface = nStartInterface; + return _pParentPool->SeekSlot( nStartInterface ); + } + + // find the first func-def with the current group id + sal_uInt16 nCount = _vInterfaces.size() + nFirstInterface; + for ( _nCurInterface = nStartInterface; + _nCurInterface < nCount; + ++_nCurInterface ) + { + SfxInterface* pInterface = _vInterfaces[_nCurInterface-nFirstInterface]; + for ( _nCurMsg = 0; + _nCurMsg < pInterface->Count(); + ++_nCurMsg ) + { + const SfxSlot& rMsg = pInterface->pSlots[_nCurMsg]; + if (rMsg.GetGroupId() == _vGroups.at(_nCurGroup)) + return &rMsg; + } + } + + return nullptr; +} + + +// skips to the next func in the current group + +const SfxSlot* SfxSlotPool::NextSlot() +{ + // The numbering starts at the interfaces of the parent pool + sal_uInt16 nFirstInterface = _pParentPool ? _pParentPool->_vInterfaces.size() : 0; + + if ( _nCurInterface < nFirstInterface && _nCurGroup >= _pParentPool->_vGroups.size() ) + _nCurInterface = nFirstInterface; + + if ( _nCurInterface < nFirstInterface ) + { + SAL_WARN_IF(!_pParentPool, "sfx.control", "No parent pool!"); + const SfxSlot *pSlot = _pParentPool->NextSlot(); + _nCurInterface = _pParentPool->_nCurInterface; + if ( pSlot ) + return pSlot; + if ( _nCurInterface == nFirstInterface ) + // parent pool is ready + return SeekSlot( nFirstInterface ); + } + + sal_uInt16 nInterface = _nCurInterface - nFirstInterface; + // possibly we are already at the end + if ( nInterface >= _vInterfaces.size() ) + return nullptr; + + // look for further matching func-defs within the same Interface + SfxInterface* pInterface = _vInterfaces[nInterface]; + while ( ++_nCurMsg < pInterface->Count() ) + { + SfxSlot& rMsg = pInterface->pSlots[_nCurMsg]; + if (rMsg.GetGroupId() == _vGroups.at(_nCurGroup)) + return &rMsg; + } + + return SeekSlot(++_nCurInterface ); +} + + +// Query SlotName with help text + + +const SfxSlot* SfxSlotPool::GetUnoSlot( const OUString& rName ) const +{ + const SfxSlot *pSlot = nullptr; + for (auto const & nInterface: _vInterfaces) + { + pSlot = nInterface->GetSlot( rName ); + if ( pSlot ) + break; + } + + if ( !pSlot && _pParentPool ) + pSlot = _pParentPool->GetUnoSlot( rName ); + + return pSlot; +} + +SfxSlotPool& SfxSlotPool::GetSlotPool( SfxViewFrame *pFrame ) +{ + SfxModule *pMod = SfxModule::GetActiveModule( pFrame ); + if ( pMod && pMod->GetSlotPool() ) + return *pMod->GetSlotPool(); + else + return *SfxGetpApp()->Get_Impl()->pSlotPool; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/objface.cxx b/sfx2/source/control/objface.cxx new file mode 100644 index 000000000..5938b68f0 --- /dev/null +++ b/sfx2/source/control/objface.cxx @@ -0,0 +1,443 @@ +/* -*- 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 <assert.h> +#include <stdlib.h> + +#include <sal/log.hxx> + +#include <sfx2/module.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/app.hxx> +#include <sfx2/msgpool.hxx> + +extern "C" { + +static int +SfxCompareSlots_qsort( const void* pSmaller, const void* pBigger ) +{ + return static_cast<int>(static_cast<SfxSlot const *>(pSmaller)->GetSlotId()) - + static_cast<int>(static_cast<SfxSlot const *>(pBigger)->GetSlotId()); +} + +static int +SfxCompareSlots_bsearch( const void* pSmaller, const void* pBigger ) +{ + return static_cast<int>(*static_cast<sal_uInt16 const *>(pSmaller)) - + static_cast<int>(static_cast<SfxSlot const *>(pBigger)->GetSlotId()); +} + +} + +namespace { + +struct SfxObjectUI_Impl +{ + sal_uInt16 nPos; + SfxVisibilityFlags nFlags; + sal_uInt32 nObjId; + bool bContext; + SfxShellFeature nFeature; + + SfxObjectUI_Impl(sal_uInt16 n, SfxVisibilityFlags f, sal_uInt32 nId, SfxShellFeature nFeat) : + nPos(n), + nFlags(f), + nObjId(nId), + bContext(false), + nFeature(nFeat) + { + } +}; + +} + +struct SfxInterface_Impl +{ + std::vector<SfxObjectUI_Impl> + aObjectBars; // registered ObjectBars + std::vector<SfxObjectUI_Impl> + aChildWindows; // registered ChildWindows + OUString aPopupName; // registered PopupMenu + StatusBarId eStatBarResId; // registered StatusBar + + SfxInterface_Impl() + : eStatBarResId(StatusBarId::None) + { + } +}; + +static SfxObjectUI_Impl CreateObjectBarUI_Impl(sal_uInt16 nPos, SfxVisibilityFlags nFlags, ToolbarId eId, SfxShellFeature nFeature); + +// constructor, registers a new unit +SfxInterface::SfxInterface( const char *pClassName, + bool bUsableSuperClass, + SfxInterfaceId nId, + const SfxInterface* pParent, + SfxSlot &rSlotMap, sal_uInt16 nSlotCount ): + pName(pClassName), + pGenoType(pParent), + nClassId(nId), + bSuperClass(bUsableSuperClass), + pImplData(new SfxInterface_Impl) +{ + SetSlotMap( rSlotMap, nSlotCount ); +} + +void SfxInterface::Register( const SfxModule* pMod ) +{ + if ( pMod ) + pMod->GetSlotPool()->RegisterInterface(*this); + else + SfxGetpApp()->GetAppSlotPool_Impl().RegisterInterface(*this); +} + +void SfxInterface::SetSlotMap( SfxSlot& rSlotMap, sal_uInt16 nSlotCount ) +{ + pSlots = &rSlotMap; + nCount = nSlotCount; + SfxSlot* pIter = pSlots; + if ( 1 == nCount && !pIter->pNextSlot ) + pIter->pNextSlot = pIter; + + if ( !pIter->pNextSlot ) + { + // sort the SfxSlots by id + qsort( pSlots, nCount, sizeof(SfxSlot), SfxCompareSlots_qsort ); + + // link masters and slaves + sal_uInt16 nIter = 1; + for ( pIter = pSlots; nIter <= nCount; ++pIter, ++nIter ) + { + + assert( nIter == nCount || + pIter->GetSlotId() != (pIter+1)->GetSlotId() ); + + if ( nullptr == pIter->GetNextSlot() ) + { + // Slots referring in circle to the next with the same + // Status method. + SfxSlot *pLastSlot = pIter; + for ( sal_uInt16 n = nIter; n < Count(); ++n ) + { + SfxSlot *pCurSlot = pSlots+n; + if ( pCurSlot->GetStateFnc() == pIter->GetStateFnc() ) + { + pLastSlot->pNextSlot = pCurSlot; + pLastSlot = pCurSlot; + } + } + pLastSlot->pNextSlot = pIter; + } + } + } +#ifdef DBG_UTIL + else + { + sal_uInt16 nIter = 1; + for ( SfxSlot *pNext = pIter+1; nIter < nCount; ++pNext, ++nIter ) + { + + if ( pNext->GetSlotId() <= pIter->GetSlotId() ) + SAL_WARN( "sfx.control", "Wrong order" ); + + const SfxSlot *pCurSlot = pIter; + do + { + pCurSlot = pCurSlot->pNextSlot; + if ( pCurSlot->GetStateFnc() != pIter->GetStateFnc() ) + { + SAL_WARN("sfx.control", "Linked Slots with different State Methods : " + << pCurSlot->GetSlotId() + << " , " << pIter->GetSlotId() ); + } + } + while ( pCurSlot != pIter ); + + pIter = pNext; + } + } +#endif +} + + +SfxInterface::~SfxInterface() +{ +} + + +// searches for the specified func + +const SfxSlot* SfxInterface::GetSlot( sal_uInt16 nFuncId ) const +{ + + assert( pSlots ); + assert( nCount ); + + // find the id using binary search + void* p = bsearch( &nFuncId, pSlots, nCount, sizeof(SfxSlot), + SfxCompareSlots_bsearch ); + if ( !p && pGenoType ) + return pGenoType->GetSlot( nFuncId ); + + return static_cast<const SfxSlot*>(p); +} + +const SfxSlot* SfxInterface::GetSlot( const OUString& rCommand ) const +{ + static const char UNO_COMMAND[] = ".uno:"; + + OUString aCommand( rCommand ); + if ( aCommand.startsWith( UNO_COMMAND ) ) + aCommand = aCommand.copy( sizeof( UNO_COMMAND )-1 ); + + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + if ( (pSlots+n)->pUnoName && + aCommand.compareToIgnoreAsciiCaseAscii( (pSlots+n)->GetUnoName() ) == 0 ) + return pSlots+n; + } + + return pGenoType ? pGenoType->GetSlot( aCommand ) : nullptr; +} + + +const SfxSlot* SfxInterface::GetRealSlot( const SfxSlot *pSlot ) const +{ + + assert( pSlots ); + assert( nCount ); + + if ( !ContainsSlot_Impl(pSlot) ) + { + if(pGenoType) + return pGenoType->GetRealSlot(pSlot); + SAL_WARN( "sfx.control", "unknown Slot" ); + return nullptr; + } + + return nullptr; +} + + +void SfxInterface::RegisterPopupMenu( const OUString& rResourceName ) +{ + pImplData->aPopupName = rResourceName; +} + +void SfxInterface::RegisterObjectBar(sal_uInt16 nPos, SfxVisibilityFlags nFlags, ToolbarId eId) +{ + RegisterObjectBar(nPos, nFlags, eId, SfxShellFeature::NONE); +} + +void SfxInterface::RegisterObjectBar(sal_uInt16 nPos, SfxVisibilityFlags nFlags, ToolbarId eId, SfxShellFeature nFeature) +{ + pImplData->aObjectBars.emplace_back( CreateObjectBarUI_Impl(nPos, nFlags, eId, nFeature) ); +} + +SfxObjectUI_Impl CreateObjectBarUI_Impl(sal_uInt16 nPos, SfxVisibilityFlags nFlags, ToolbarId eId, SfxShellFeature nFeature) +{ + if (nFlags == SfxVisibilityFlags::Invisible) + nFlags |= SfxVisibilityFlags::Standard; + + return SfxObjectUI_Impl(nPos, nFlags, static_cast<sal_uInt32>(eId), nFeature); +} + +ToolbarId SfxInterface::GetObjectBarId(sal_uInt16 nNo) const +{ + bool bGenoType = (pGenoType != nullptr && pGenoType->UseAsSuperClass()); + if ( bGenoType ) + { + // Are there toolbars in the super class? + sal_uInt16 nBaseCount = pGenoType->GetObjectBarCount(); + if ( nNo < nBaseCount ) + // The Super class comes first + return pGenoType->GetObjectBarId(nNo); + else + nNo = nNo - nBaseCount; + } + + assert( nNo<pImplData->aObjectBars.size() ); + + return static_cast<ToolbarId>(pImplData->aObjectBars[nNo].nObjId); +} + +sal_uInt16 SfxInterface::GetObjectBarPos( sal_uInt16 nNo ) const +{ + bool bGenoType = (pGenoType != nullptr && pGenoType->UseAsSuperClass()); + if ( bGenoType ) + { + // Are there toolbars in the super class? + sal_uInt16 nBaseCount = pGenoType->GetObjectBarCount(); + if ( nNo < nBaseCount ) + // The Super class comes first + return pGenoType->GetObjectBarPos( nNo ); + else + nNo = nNo - nBaseCount; + } + + assert( nNo<pImplData->aObjectBars.size() ); + + return pImplData->aObjectBars[nNo].nPos; +} + +SfxVisibilityFlags SfxInterface::GetObjectBarFlags( sal_uInt16 nNo ) const +{ + bool bGenoType = (pGenoType != nullptr && pGenoType->UseAsSuperClass()); + if ( bGenoType ) + { + // Are there toolbars in the super class? + sal_uInt16 nBaseCount = pGenoType->GetObjectBarCount(); + if ( nNo < nBaseCount ) + // The Super class comes first + return pGenoType->GetObjectBarFlags( nNo ); + else + nNo = nNo - nBaseCount; + } + + assert( nNo<pImplData->aObjectBars.size() ); + + return pImplData->aObjectBars[nNo].nFlags; +} + +sal_uInt16 SfxInterface::GetObjectBarCount() const +{ + if (pGenoType && pGenoType->UseAsSuperClass()) + return pImplData->aObjectBars.size() + pGenoType->GetObjectBarCount(); + else + return pImplData->aObjectBars.size(); +} + +void SfxInterface::RegisterChildWindow(sal_uInt16 nId, bool bContext) +{ + RegisterChildWindow(nId, bContext, SfxShellFeature::NONE); +} + +void SfxInterface::RegisterChildWindow(sal_uInt16 nId, bool bContext, SfxShellFeature nFeature) +{ + SfxObjectUI_Impl aUI(0, SfxVisibilityFlags::Invisible, nId, nFeature); + aUI.bContext = bContext; + pImplData->aChildWindows.emplace_back(aUI); +} + +void SfxInterface::RegisterStatusBar(StatusBarId eId) +{ + pImplData->eStatBarResId = eId; +} + +sal_uInt32 SfxInterface::GetChildWindowId (sal_uInt16 nNo) const +{ + if ( pGenoType ) + { + // Are there ChildWindows in the superclass? + sal_uInt16 nBaseCount = pGenoType->GetChildWindowCount(); + if ( nNo < nBaseCount ) + // The Super class comes first + return pGenoType->GetChildWindowId( nNo ); + else + nNo = nNo - nBaseCount; + } + + assert( nNo<pImplData->aChildWindows.size() ); + + sal_uInt32 nRet = pImplData->aChildWindows[nNo].nObjId; + if ( pImplData->aChildWindows[nNo].bContext ) + nRet += sal_uInt16( nClassId ) << 16; + return nRet; +} + +SfxShellFeature SfxInterface::GetChildWindowFeature (sal_uInt16 nNo) const +{ + if ( pGenoType ) + { + // Are there ChildWindows in the superclass? + sal_uInt16 nBaseCount = pGenoType->GetChildWindowCount(); + if ( nNo < nBaseCount ) + // The Super class comes first + return pGenoType->GetChildWindowFeature( nNo ); + else + nNo = nNo - nBaseCount; + } + + assert( nNo<pImplData->aChildWindows.size() ); + + return pImplData->aChildWindows[nNo].nFeature; +} + + +sal_uInt16 SfxInterface::GetChildWindowCount() const +{ + if (pGenoType) + return pImplData->aChildWindows.size() + pGenoType->GetChildWindowCount(); + else + return pImplData->aChildWindows.size(); +} + +const OUString& SfxInterface::GetPopupMenuName() const +{ + return pImplData->aPopupName; +} + +StatusBarId SfxInterface::GetStatusBarId() const +{ + if (pImplData->eStatBarResId == StatusBarId::None && pGenoType) + return pGenoType->GetStatusBarId(); + else + return pImplData->eStatBarResId; +} + +SfxShellFeature SfxInterface::GetObjectBarFeature ( sal_uInt16 nNo ) const +{ + bool bGenoType = (pGenoType != nullptr && pGenoType->UseAsSuperClass()); + if ( bGenoType ) + { + // Are there toolbars in the super class? + sal_uInt16 nBaseCount = pGenoType->GetObjectBarCount(); + if ( nNo < nBaseCount ) + // The Super class comes first + return pGenoType->GetObjectBarFeature( nNo ); + else + nNo = nNo - nBaseCount; + } + + assert( nNo<pImplData->aObjectBars.size() ); + + return pImplData->aObjectBars[nNo].nFeature; +} + +bool SfxInterface::IsObjectBarVisible(sal_uInt16 nNo) const +{ + bool bGenoType = (pGenoType != nullptr && pGenoType->UseAsSuperClass()); + if ( bGenoType ) + { + // Are there toolbars in the super class? + sal_uInt16 nBaseCount = pGenoType->GetObjectBarCount(); + if ( nNo < nBaseCount ) + // The Super class comes first + return pGenoType->IsObjectBarVisible( nNo ); + else + nNo = nNo - nBaseCount; + } + + assert( nNo<pImplData->aObjectBars.size() ); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/recentdocsview.cxx b/sfx2/source/control/recentdocsview.cxx new file mode 100644 index 000000000..b7a6ba4ad --- /dev/null +++ b/sfx2/source/control/recentdocsview.cxx @@ -0,0 +1,314 @@ +/* -*- 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/log.hxx> +#include <recentdocsview.hxx> +#include <sfx2/sfxresid.hxx> +#include <unotools/historyoptions.hxx> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/svapp.hxx> +#include <tools/urlobj.hxx> +#include <com/sun/star/frame/XDispatch.hpp> +#include <sfx2/strings.hrc> +#include <bitmaps.hlst> +#include "recentdocsviewitem.hxx" +#include <sfx2/app.hxx> + +#include <officecfg/Office/Common.hxx> + +#include <map> + +using namespace ::com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; + +namespace { + +/// Set (larger) font for the Welcome message. +void SetMessageFont(vcl::RenderContext& rRenderContext) +{ + vcl::Font aFont(rRenderContext.GetFont()); + aFont.SetFontHeight(aFont.GetFontHeight() * 1.3); + rRenderContext.SetFont(aFont); +} + +} + +namespace sfx2 +{ + +constexpr tools::Long gnTextHeight = 30; +constexpr tools::Long gnItemPadding = 5; + +RecentDocsView::RecentDocsView(std::unique_ptr<weld::ScrolledWindow> xWindow, std::unique_ptr<weld::Menu> xMenu) + : ThumbnailView(std::move(xWindow), std::move(xMenu)) + , mnFileTypes(ApplicationType::TYPE_NONE) + , mnLastMouseDownItem(THUMBNAILVIEW_ITEM_NOTFOUND) + , maWelcomeLine1(SfxResId(STR_WELCOME_LINE1)) + , maWelcomeLine2(SfxResId(STR_WELCOME_LINE2)) + , mpLoadRecentFile(nullptr) + , m_nExecuteHdlId(nullptr) +{ + tools::Rectangle aScreen = Application::GetScreenPosSizePixel(Application::GetDisplayBuiltInScreen()); + mnItemMaxSize = std::min(aScreen.GetWidth(),aScreen.GetHeight()) > 800 ? 256 : 192; + + setItemMaxTextLength( 30 ); + setItemDimensions( mnItemMaxSize, mnItemMaxSize, gnTextHeight, gnItemPadding ); + + maFillColor = Color(ColorTransparency, officecfg::Office::Common::Help::StartCenter::StartCenterThumbnailsBackgroundColor::get()); + maTextColor = Color(ColorTransparency, officecfg::Office::Common::Help::StartCenter::StartCenterThumbnailsTextColor::get()); + maHighlightColor = Color(ColorTransparency, officecfg::Office::Common::Help::StartCenter::StartCenterThumbnailsHighlightColor::get()); + maHighlightTextColor = Color(ColorTransparency, officecfg::Office::Common::Help::StartCenter::StartCenterThumbnailsHighlightTextColor::get()); + mfHighlightTransparence = 0.25; + + UpdateColors(); +} + +RecentDocsView::~RecentDocsView() +{ + Application::RemoveUserEvent(m_nExecuteHdlId); + m_nExecuteHdlId = nullptr; + if (mpLoadRecentFile) + { + mpLoadRecentFile->pView = nullptr; + mpLoadRecentFile = nullptr; + } +} + +bool RecentDocsView::typeMatchesExtension(ApplicationType type, std::u16string_view rExt) +{ + bool bRet = false; + + if (rExt == u"odt" || rExt == u"fodt" || rExt == u"doc" || rExt == u"docx" || + rExt == u"rtf" || rExt == u"txt" || rExt == u"odm" || rExt == u"otm") + { + bRet = static_cast<bool>(type & ApplicationType::TYPE_WRITER); + } + else if (rExt == u"ods" || rExt == u"fods" || rExt == u"xls" || rExt == u"xlsx") + { + bRet = static_cast<bool>(type & ApplicationType::TYPE_CALC); + } + else if (rExt == u"odp" || rExt == u"fodp" || rExt == u"pps" || rExt == u"ppt" || + rExt == u"pptx") + { + bRet = static_cast<bool>(type & ApplicationType::TYPE_IMPRESS); + } + else if (rExt == u"odg" || rExt == u"fodg") + { + bRet = static_cast<bool>(type & ApplicationType::TYPE_DRAW); + } + else if (rExt == u"odb") + { + bRet = static_cast<bool>(type & ApplicationType::TYPE_DATABASE); + } + else if (rExt == u"odf") + { + bRet = static_cast<bool>(type & ApplicationType::TYPE_MATH); + } + else + { + bRet = static_cast<bool>(type & ApplicationType::TYPE_OTHER); + } + + return bRet; +} + +bool RecentDocsView::isAcceptedFile(const INetURLObject& rURL) const +{ + const OUString aExt = rURL.getExtension(); + return (mnFileTypes & ApplicationType::TYPE_WRITER && typeMatchesExtension(ApplicationType::TYPE_WRITER, aExt)) || + (mnFileTypes & ApplicationType::TYPE_CALC && typeMatchesExtension(ApplicationType::TYPE_CALC, aExt)) || + (mnFileTypes & ApplicationType::TYPE_IMPRESS && typeMatchesExtension(ApplicationType::TYPE_IMPRESS, aExt)) || + (mnFileTypes & ApplicationType::TYPE_DRAW && typeMatchesExtension(ApplicationType::TYPE_DRAW, aExt)) || + (mnFileTypes & ApplicationType::TYPE_DATABASE && typeMatchesExtension(ApplicationType::TYPE_DATABASE,aExt)) || + (mnFileTypes & ApplicationType::TYPE_MATH && typeMatchesExtension(ApplicationType::TYPE_MATH, aExt)) || + (mnFileTypes & ApplicationType::TYPE_OTHER && typeMatchesExtension(ApplicationType::TYPE_OTHER, aExt)); +} + +void RecentDocsView::insertItem(const OUString &rURL, const OUString &rTitle, const OUString& rThumbnail, bool isReadOnly, sal_uInt16 nId) +{ + AppendItem( std::make_unique<RecentDocsViewItem>(*this, rURL, rTitle, rThumbnail, nId, mnItemMaxSize, isReadOnly) ); +} + +void RecentDocsView::Reload() +{ + Clear(); + + std::vector< SvtHistoryOptions::HistoryItem > aHistoryList = SvtHistoryOptions::GetList( EHistoryType::PickList ); + for ( size_t i = 0; i < aHistoryList.size(); i++ ) + { + const SvtHistoryOptions::HistoryItem& rRecentEntry = aHistoryList[i]; + + OUString aURL = rRecentEntry.sURL; + const INetURLObject aURLObj(aURL); + + if (!isAcceptedFile(aURLObj)) + continue; + + //Remove extension from url's last segment and use it as title + const OUString aTitle = aURLObj.GetBase(); //DecodeMechanism::WithCharset + + insertItem(aURL, aTitle, rRecentEntry.sThumbnail, rRecentEntry.isReadOnly, i+1); + } + + CalculateItemPositions(); + Invalidate(); +} + +bool RecentDocsView::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (rMEvt.IsLeft()) + { + mnLastMouseDownItem = ImplGetItem(rMEvt.GetPosPixel()); + + // ignore to avoid stuff done in ThumbnailView; we don't do selections etc. + return true; + } + + return ThumbnailView::MouseButtonDown(rMEvt); +} + +bool RecentDocsView::MouseButtonUp(const MouseEvent& rMEvt) +{ + if (rMEvt.IsLeft()) + { + if( rMEvt.GetClicks() > 1 ) + return true; + + size_t nPos = ImplGetItem(rMEvt.GetPosPixel()); + ThumbnailViewItem* pItem = ImplGetItem(nPos); + + if (pItem && nPos == mnLastMouseDownItem) + { + pItem->MouseButtonUp(rMEvt); + + ThumbnailViewItem* pNewItem = ImplGetItem(nPos); + if(pNewItem) + pNewItem->setHighlight(true); + } + + mnLastMouseDownItem = THUMBNAILVIEW_ITEM_NOTFOUND; + + if (pItem) + return true; + } + return ThumbnailView::MouseButtonUp(rMEvt); +} + +void RecentDocsView::OnItemDblClicked(ThumbnailViewItem *pItem) +{ + RecentDocsViewItem* pRecentItem = dynamic_cast< RecentDocsViewItem* >(pItem); + if (pRecentItem) + pRecentItem->OpenDocument(); +} + +void RecentDocsView::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &aRect) +{ + ThumbnailView::Paint(rRenderContext, aRect); + + if (!mItemList.empty()) + return; + + if (maWelcomeImage.IsEmpty()) + { + const tools::Long aWidth(aRect.GetWidth() > aRect.getHeight() ? aRect.GetHeight()/2 : aRect.GetWidth()/2); + maWelcomeImage = SfxApplication::GetApplicationLogo(aWidth); + } + + // No recent files to be shown yet. Show a welcome screen. + rRenderContext.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR); + SetMessageFont(rRenderContext); + rRenderContext.SetTextColor(maTextColor); + + tools::Long nTextHeight = rRenderContext.GetTextHeight(); + + const Size& rImgSize = maWelcomeImage.GetSizePixel(); + const Size& rSize = GetOutputSizePixel(); + + const int nX = (rSize.Width() - rImgSize.Width())/2; + int nY = (rSize.Height() - 3 * nTextHeight - rImgSize.Height())/2; + Point aImgPoint(nX, nY); + rRenderContext.DrawBitmapEx(aImgPoint, rImgSize, maWelcomeImage); + + nY = nY + rImgSize.Height(); + rRenderContext.DrawText(tools::Rectangle(0, nY + 1 * nTextHeight, rSize.Width(), nY + nTextHeight), + maWelcomeLine1, + DrawTextFlags::Center); + rRenderContext.DrawText(tools::Rectangle(0, nY + 2 * nTextHeight, rSize.Width(), rSize.Height()), + maWelcomeLine2, + DrawTextFlags::MultiLine | DrawTextFlags::WordBreak | DrawTextFlags::Center); + + rRenderContext.Pop(); +} + +void RecentDocsView::LoseFocus() +{ + deselectItems(); + + ThumbnailView::LoseFocus(); +} + +void RecentDocsView::Clear() +{ + Invalidate(); + ThumbnailView::Clear(); +} + +void RecentDocsView::PostLoadRecentUsedFile(LoadRecentFile* pLoadRecentFile) +{ + assert(!mpLoadRecentFile); + mpLoadRecentFile = pLoadRecentFile; + m_nExecuteHdlId = Application::PostUserEvent(LINK(this, RecentDocsView, ExecuteHdl_Impl), pLoadRecentFile); +} + +void RecentDocsView::DispatchedLoadRecentUsedFile() +{ + mpLoadRecentFile = nullptr; +} + +IMPL_LINK( RecentDocsView, ExecuteHdl_Impl, void*, p, void ) +{ + m_nExecuteHdlId = nullptr; + LoadRecentFile* pLoadRecentFile = static_cast<LoadRecentFile*>(p); + try + { + // Asynchronous execution as this can lead to our own destruction! + // Framework can recycle our current frame and the layout manager disposes all user interface + // elements if a component gets detached from its frame! + pLoadRecentFile->xDispatch->dispatch( pLoadRecentFile->aTargetURL, pLoadRecentFile->aArgSeq ); + } + catch ( const Exception& ) + { + } + + if (pLoadRecentFile->pView) + { + pLoadRecentFile->pView->DispatchedLoadRecentUsedFile(); + pLoadRecentFile->pView->SetPointer(PointerStyle::Arrow); + pLoadRecentFile->pView->Enable(); + } + + delete pLoadRecentFile; +} + +} // namespace sfx2 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/recentdocsviewitem.cxx b/sfx2/source/control/recentdocsviewitem.cxx new file mode 100644 index 000000000..44f103dfb --- /dev/null +++ b/sfx2/source/control/recentdocsviewitem.cxx @@ -0,0 +1,348 @@ +/* -*- 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/. + */ + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/StorageFactory.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <comphelper/base64.hxx> +#include <comphelper/propertyvalue.hxx> +#include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <i18nutil/paper.hxx> +#include <officecfg/Office/Common.hxx> +#include <recentdocsview.hxx> +#include <sfx2/templatelocalview.hxx> +#include <tools/diagnose_ex.h> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <unotools/historyoptions.hxx> +#include <vcl/event.hxx> +#include <vcl/filter/PngImageReader.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/virdev.hxx> + +#include <map> + +#include <bitmaps.hlst> +#include "recentdocsviewitem.hxx" + +using namespace basegfx; +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace drawinglayer::primitive2d; +using namespace drawinglayer::processor2d; + +namespace +{ +bool IsDocEncrypted(const OUString& rURL) +{ + bool bIsEncrypted = false; + + try + { + auto xFactory = embed::StorageFactory::create(comphelper::getProcessComponentContext()); + auto xStorage(xFactory->createInstanceWithArguments( + { uno::Any(rURL), uno::Any(embed::ElementModes::READ) })); + if (uno::Reference<beans::XPropertySet> xStorageProps{ xStorage, uno::UNO_QUERY }) + { + try + { + xStorageProps->getPropertyValue("HasEncryptedEntries") >>= bIsEncrypted; + } + catch (uno::Exception&) + { + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sfx", "caught exception trying to find out if doc <" + << rURL << "> is encrypted:"); + } + + return bIsEncrypted; +} + +using Ext2IconMap = std::map<sfx2::ApplicationType, OUString>; +BitmapEx Url2Icon(std::u16string_view rURL, const Ext2IconMap& rExtToIcon, const OUString& sDefault) +{ + auto it = std::find_if(rExtToIcon.begin(), rExtToIcon.end(), + [aExt = INetURLObject(rURL).getExtension()](const auto& r) + { return sfx2::RecentDocsView::typeMatchesExtension(r.first, aExt); }); + + return BitmapEx(it != rExtToIcon.end() ? it->second : sDefault); +}; + +BitmapEx getDefaultThumbnail(const OUString& rURL) +{ + static const Ext2IconMap BitmapForExtension + = { { sfx2::ApplicationType::TYPE_WRITER, SFX_FILE_THUMBNAIL_TEXT }, + { sfx2::ApplicationType::TYPE_CALC, SFX_FILE_THUMBNAIL_SHEET }, + { sfx2::ApplicationType::TYPE_IMPRESS, SFX_FILE_THUMBNAIL_PRESENTATION }, + { sfx2::ApplicationType::TYPE_DRAW, SFX_FILE_THUMBNAIL_DRAWING }, + { sfx2::ApplicationType::TYPE_DATABASE, SFX_FILE_THUMBNAIL_DATABASE }, + { sfx2::ApplicationType::TYPE_MATH, SFX_FILE_THUMBNAIL_MATH } }; + + static const Ext2IconMap EncryptedBitmapForExtension + = { { sfx2::ApplicationType::TYPE_WRITER, BMP_128X128_WRITER_DOC }, + { sfx2::ApplicationType::TYPE_CALC, BMP_128X128_CALC_DOC }, + { sfx2::ApplicationType::TYPE_IMPRESS, BMP_128X128_IMPRESS_DOC }, + { sfx2::ApplicationType::TYPE_DRAW, BMP_128X128_DRAW_DOC }, + // You can't save a database file with encryption -> no respective icon + { sfx2::ApplicationType::TYPE_MATH, BMP_128X128_MATH_DOC } }; + + const std::map<sfx2::ApplicationType, OUString>& rWhichMap + = IsDocEncrypted(rURL) ? EncryptedBitmapForExtension : BitmapForExtension; + + return Url2Icon(rURL, rWhichMap, SFX_FILE_THUMBNAIL_DEFAULT); +} + +BitmapEx getModuleOverlay(std::u16string_view rURL) +{ + static const Ext2IconMap OverlayBitmapForExtension + = { { sfx2::ApplicationType::TYPE_WRITER, SFX_FILE_OVERLAY_TEXT }, + { sfx2::ApplicationType::TYPE_CALC, SFX_FILE_OVERLAY_SHEET }, + { sfx2::ApplicationType::TYPE_IMPRESS, SFX_FILE_OVERLAY_PRESENTATION }, + { sfx2::ApplicationType::TYPE_DRAW, SFX_FILE_OVERLAY_DRAWING }, + { sfx2::ApplicationType::TYPE_DATABASE, SFX_FILE_OVERLAY_DATABASE }, + { sfx2::ApplicationType::TYPE_MATH, SFX_FILE_OVERLAY_MATH } }; + + return Url2Icon(rURL, OverlayBitmapForExtension, SFX_FILE_OVERLAY_DEFAULT); +} +}; + +RecentDocsViewItem::RecentDocsViewItem(sfx2::RecentDocsView &rView, const OUString &rURL, + const OUString &rTitle, std::u16string_view const sThumbnailBase64, + sal_uInt16 const nId, tools::Long const nThumbnailSize, + bool const isReadOnly) + : ThumbnailViewItem(rView, nId), + mrParentView(rView), + maURL(rURL), + m_isReadOnly(isReadOnly), + m_bRemoveIconHighlighted(false), + m_aRemoveRecentBitmap(BMP_RECENTDOC_REMOVE), + m_aRemoveRecentBitmapHighlighted(BMP_RECENTDOC_REMOVE_HIGHLIGHTED) +{ + OUString aTitle(rTitle); + INetURLObject aURLObj(rURL); + + if( aURLObj.GetProtocol() == INetProtocol::File ) + m_sHelpText = aURLObj.getFSysPath(FSysStyle::Detect); + if( m_sHelpText.isEmpty() ) + m_sHelpText = aURLObj.GetURLNoPass(); + + if (aTitle.isEmpty()) + aTitle = aURLObj.GetLastName(INetURLObject::DecodeMechanism::WithCharset); + + BitmapEx aThumbnail; + + //fdo#74834: only load thumbnail if the corresponding option is not disabled in the configuration + if (officecfg::Office::Common::History::RecentDocsThumbnail::get()) + { + if (!sThumbnailBase64.empty()) + { + Sequence<sal_Int8> aDecoded; + comphelper::Base64::decode(aDecoded, sThumbnailBase64); + + SvMemoryStream aStream(aDecoded.getArray(), aDecoded.getLength(), StreamMode::READ); + vcl::PngImageReader aReader(aStream); + aThumbnail = aReader.read(); + } + else if (sfx2::RecentDocsView::typeMatchesExtension(sfx2::ApplicationType::TYPE_DATABASE, + aURLObj.getExtension())) + { + aThumbnail + = BitmapEx(nThumbnailSize > 192 ? SFX_THUMBNAIL_BASE_256 : SFX_THUMBNAIL_BASE_192); + } + } + + if (aThumbnail.IsEmpty()) + { + // 1. Thumbnail absent: get the default thumbnail, checking for encryption. + BitmapEx aExt(getDefaultThumbnail(rURL)); + Size aExtSize(aExt.GetSizePixel()); + + // attempt to make it appear as if it is on a piece of paper + tools::Long nPaperHeight; + tools::Long nPaperWidth; + if (sfx2::RecentDocsView::typeMatchesExtension( + sfx2::ApplicationType::TYPE_IMPRESS, aURLObj.getExtension())) + { + // Swap width and height (PAPER_SCREEN_4_3 definition make it needed) + PaperInfo aInfo(PAPER_SCREEN_4_3); + nPaperHeight = aInfo.getWidth(); + nPaperWidth = aInfo.getHeight(); + } + else + { + PaperInfo aInfo(PaperInfo::getSystemDefaultPaper()); + nPaperHeight = aInfo.getHeight(); + nPaperWidth = aInfo.getWidth(); + } + double ratio = double(nThumbnailSize) / double(std::max(nPaperHeight, nPaperWidth)); + Size aThumbnailSize(std::round(nPaperWidth * ratio), std::round(nPaperHeight * ratio)); + + if (aExtSize.Width() > aThumbnailSize.Width() || aExtSize.Height() > aThumbnailSize.Height()) + { + aExt = TemplateLocalView::scaleImg(aExt, aThumbnailSize.Width(), aThumbnailSize.Height()); + aExtSize = aExt.GetSizePixel(); + } + + // create empty, and copy the default thumbnail in + sal_uInt8 nAlpha = 255; + aThumbnail = BitmapEx(Bitmap(aThumbnailSize, vcl::PixelFormat::N24_BPP), AlphaMask(aThumbnailSize, &nAlpha)); + + aThumbnail.CopyPixel( + ::tools::Rectangle(Point((aThumbnailSize.Width() - aExtSize.Width()) / 2, (aThumbnailSize.Height() - aExtSize.Height()) / 2), aExtSize), + ::tools::Rectangle(Point(0, 0), aExtSize), + &aExt); + } + else + { + // 2. Thumbnail present: it's unencrypted document -> add a module overlay. + // Pre-scale the thumbnail to the final size before applying the overlay + aThumbnail = TemplateLocalView::scaleImg(aThumbnail, nThumbnailSize, nThumbnailSize); + + BitmapEx aModule = getModuleOverlay(rURL); + if (!aModule.IsEmpty()) + { + const Size aSize(aThumbnail.GetSizePixel()); + const Size aOverlaySize(aModule.GetSizePixel()); + ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create()); + pVirDev->SetOutputSizePixel(aSize); + pVirDev->DrawBitmapEx(Point(), aThumbnail); + pVirDev->DrawBitmapEx(Point(aSize.Width() - aOverlaySize.Width() - 5, + aSize.Height() - aOverlaySize.Height() - 5), + aModule); + aThumbnail = pVirDev->GetBitmapEx(Point(), aSize); + } + } + + maTitle = aTitle; + maPreview1 = aThumbnail; +} + +::tools::Rectangle RecentDocsViewItem::updateHighlight(bool bVisible, const Point& rPoint) +{ + ::tools::Rectangle aRect(ThumbnailViewItem::updateHighlight(bVisible, rPoint)); + + if (bVisible && getRemoveIconArea().Contains(rPoint)) + { + if (!m_bRemoveIconHighlighted) + aRect.Union(getRemoveIconArea()); + + m_bRemoveIconHighlighted = true; + } + else + { + if (m_bRemoveIconHighlighted) + aRect.Union(getRemoveIconArea()); + + m_bRemoveIconHighlighted = false; + } + + return aRect; +} + +::tools::Rectangle RecentDocsViewItem::getRemoveIconArea() const +{ + ::tools::Rectangle aArea(getDrawArea()); + Size aSize(m_aRemoveRecentBitmap.GetSizePixel()); + + return ::tools::Rectangle( + Point(aArea.Right() - aSize.Width() - THUMBNAILVIEW_ITEM_CORNER, aArea.Top() + THUMBNAILVIEW_ITEM_CORNER), + aSize); +} + +OUString RecentDocsViewItem::getHelpText() const +{ + return m_sHelpText; +} + +void RecentDocsViewItem::Paint(drawinglayer::processor2d::BaseProcessor2D *pProcessor, const ThumbnailItemAttributes *pAttrs) +{ + ThumbnailViewItem::Paint(pProcessor, pAttrs); + + // paint the remove icon when highlighted + if (isHighlighted()) + { + drawinglayer::primitive2d::Primitive2DContainer aSeq(1); + + Point aIconPos(getRemoveIconArea().TopLeft()); + + aSeq[0] = drawinglayer::primitive2d::Primitive2DReference(new DiscreteBitmapPrimitive2D( + m_bRemoveIconHighlighted ? m_aRemoveRecentBitmapHighlighted : m_aRemoveRecentBitmap, + B2DPoint(aIconPos.X(), aIconPos.Y()))); + + pProcessor->process(aSeq); + } +} + +void RecentDocsViewItem::MouseButtonUp(const MouseEvent& rMEvt) +{ + if (rMEvt.IsLeft()) + { + if (getRemoveIconArea().Contains(rMEvt.GetPosPixel())) + { + SvtHistoryOptions::DeleteItem(EHistoryType::PickList, maURL); + mrParent.Reload(); + return; + } + + OpenDocument(); + return; + } +} + +void RecentDocsViewItem::OpenDocument() +{ + // show busy mouse pointer + mrParentView.SetPointer(PointerStyle::Wait); + mrParentView.Disable(); + + Reference<frame::XDispatch> xDispatch; + css::util::URL aTargetURL; + Sequence<beans::PropertyValue> aArgsList; + + uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(::comphelper::getProcessComponentContext()); + + aTargetURL.Complete = maURL; + Reference<util::XURLTransformer> xTrans(util::URLTransformer::create(::comphelper::getProcessComponentContext())); + xTrans->parseStrict(aTargetURL); + + aArgsList = { comphelper::makePropertyValue("Referer", OUString("private:user")), + // documents will never be opened as templates + comphelper::makePropertyValue("AsTemplate", false) }; + if (m_isReadOnly) // tdf#149170 only add if true + { + aArgsList.realloc(aArgsList.size()+1); + aArgsList.getArray()[aArgsList.size()-1] = comphelper::makePropertyValue("ReadOnly", true); + } + + xDispatch = xDesktop->queryDispatch(aTargetURL, "_default", 0); + + if (!xDispatch.is()) + return; + + // Call dispatch asynchronously as we can be destroyed while dispatch is + // executed. VCL is not able to survive this as it wants to call listeners + // after select!!! + sfx2::LoadRecentFile *const pLoadRecentFile = new sfx2::LoadRecentFile; + pLoadRecentFile->xDispatch = xDispatch; + pLoadRecentFile->aTargetURL = aTargetURL; + pLoadRecentFile->aArgSeq = aArgsList; + pLoadRecentFile->pView = &mrParentView; + + mrParentView.PostLoadRecentUsedFile(pLoadRecentFile); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/recentdocsviewitem.hxx b/sfx2/source/control/recentdocsviewitem.hxx new file mode 100644 index 000000000..3f5f6d3fa --- /dev/null +++ b/sfx2/source/control/recentdocsviewitem.hxx @@ -0,0 +1,67 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SFX2_RECENTDOCSVIEWITEM_HXX +#define INCLUDED_SFX2_RECENTDOCSVIEWITEM_HXX + +#include <sfx2/thumbnailviewitem.hxx> + +namespace sfx2 +{ + class RecentDocsView; +} + +class RecentDocsViewItem final : public ThumbnailViewItem +{ +public: + RecentDocsViewItem(sfx2::RecentDocsView &rView, const OUString &rURL, + const OUString &rTitle, std::u16string_view sThumbnailBase64, sal_uInt16 nId, tools::Long nThumbnailSize, bool isReadOnly); + + /** Updates own highlight status based on the aPoint position. + + Calls the ancestor's updateHighlight, and then takes care of m_bRemoveIconHighlighted. + + Returns rectangle that needs to be invalidated. + */ + virtual tools::Rectangle updateHighlight(bool bVisible, const Point& rPoint) override; + + /// Text to be used for the tooltip. + virtual OUString getHelpText() const override; + + virtual void Paint(drawinglayer::processor2d::BaseProcessor2D *pProcessor, + const ThumbnailItemAttributes *pAttrs) override; + + virtual void MouseButtonUp(const MouseEvent& rMEvt) override; + + /// Called when the user clicks a document - it will open it. + void OpenDocument(); + +private: + sfx2::RecentDocsView& mrParentView; + + /// Return area where is the icon to remove document from the recent documents. + tools::Rectangle getRemoveIconArea() const; + + OUString maURL; + + bool m_isReadOnly = false; + + OUString m_sHelpText; + + /// Is the icon that the user can click to remove the document from the recent documents highlighted? + bool m_bRemoveIconHighlighted; + + BitmapEx m_aRemoveRecentBitmap; + + BitmapEx m_aRemoveRecentBitmapHighlighted; +}; + +#endif // INCLUDED_SFX2_RECENTDOCSVIEWITEM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/request.cxx b/sfx2/source/control/request.cxx new file mode 100644 index 000000000..b43d1dd99 --- /dev/null +++ b/sfx2/source/control/request.cxx @@ -0,0 +1,763 @@ +/* -*- 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 <memory> +#include <com/sun/star/frame/DispatchStatement.hpp> +#include <com/sun/star/container/XIndexReplace.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/frame/XDispatchRecorderSupplier.hpp> +#include <svl/itemiter.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> + +#include <svl/itempool.hxx> +#include <itemdel.hxx> + +#include <comphelper/processfactory.hxx> + +#include <svl/hint.hxx> + +#include <sfx2/request.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/sfxuno.hxx> + + +using namespace ::com::sun::star; + +struct SfxRequest_Impl: public SfxListener + +/* [Description] + + Implementation structure of the <SfxRequest> class. +*/ + +{ + SfxRequest* pAnti; // Owner because of dying pool + OUString aTarget; // if possible from target object set by App + SfxItemPool* pPool; // ItemSet build with this pool + std::unique_ptr<SfxPoolItem> pRetVal; // Return value belongs to itself + SfxShell* pShell; // run from this shell + const SfxSlot* pSlot; // executed Slot + sal_uInt16 nModifier; // which Modifier was pressed? + bool bDone; // at all executed + bool bIgnored; // Cancelled by the User + bool bCancelled; // no longer notify + SfxCallMode nCallMode; // Synch/Asynch/API/Record + bool bAllowRecording; + std::unique_ptr<SfxAllItemSet> + pInternalArgs; + SfxViewFrame* pViewFrame; + + css::uno::Reference< css::frame::XDispatchRecorder > xRecorder; + css::uno::Reference< css::util::XURLTransformer > xTransform; + + explicit SfxRequest_Impl( SfxRequest *pOwner ) + : pAnti( pOwner) + , pPool(nullptr) + , pShell(nullptr) + , pSlot(nullptr) + , nModifier(0) + , bDone(false) + , bIgnored(false) + , bCancelled(false) + , nCallMode( SfxCallMode::SYNCHRON ) + , bAllowRecording( false ) + , pViewFrame(nullptr) + { + } + + void SetPool( SfxItemPool *pNewPool ); + virtual void Notify( SfxBroadcaster &rBC, const SfxHint &rHint ) override; + void Record( const uno::Sequence < beans::PropertyValue >& rArgs ); +}; + + +void SfxRequest_Impl::Notify( SfxBroadcaster&, const SfxHint &rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying ) + pAnti->Cancel(); +} + + +void SfxRequest_Impl::SetPool( SfxItemPool *pNewPool ) +{ + if ( pNewPool != pPool ) + { + if ( pPool ) + EndListening( pPool->BC() ); + pPool = pNewPool; + if ( pNewPool ) + StartListening( pNewPool->BC() ); + } +} + + +SfxRequest::~SfxRequest() +{ + // Leave out Done() marked requests with 'rem' + if ( pImpl->xRecorder.is() && !pImpl->bDone && !pImpl->bIgnored ) + pImpl->Record( uno::Sequence < beans::PropertyValue >() ); + + // Clear object + pArgs.reset(); + if ( pImpl->pRetVal ) + DeleteItemOnIdle(std::move(pImpl->pRetVal)); +} + + +SfxRequest::SfxRequest +( + const SfxRequest& rOrig +) +: SfxHint( rOrig ), + nSlot(rOrig.nSlot), + pArgs(rOrig.pArgs? new SfxAllItemSet(*rOrig.pArgs): nullptr), + pImpl( new SfxRequest_Impl(this) ) +{ + pImpl->bAllowRecording = rOrig.pImpl->bAllowRecording; + pImpl->bDone = false; + pImpl->bIgnored = false; + pImpl->pShell = nullptr; + pImpl->pSlot = nullptr; + pImpl->nCallMode = rOrig.pImpl->nCallMode; + pImpl->aTarget = rOrig.pImpl->aTarget; + pImpl->nModifier = rOrig.pImpl->nModifier; + + // deep copy needed ! + pImpl->pInternalArgs.reset( rOrig.pImpl->pInternalArgs ? new SfxAllItemSet(*rOrig.pImpl->pInternalArgs) : nullptr); + + if ( pArgs ) + pImpl->SetPool( pArgs->GetPool() ); + else + pImpl->SetPool( rOrig.pImpl->pPool ); + + // setup macro recording if it was in the original SfxRequest + if (!rOrig.pImpl->pViewFrame || !rOrig.pImpl->xRecorder.is()) + return; + + nSlot = rOrig.nSlot; + pImpl->pViewFrame = rOrig.pImpl->pViewFrame; + if (pImpl->pViewFrame->GetDispatcher()->GetShellAndSlot_Impl(nSlot, &pImpl->pShell, &pImpl->pSlot, true, true)) + { + pImpl->SetPool( &pImpl->pShell->GetPool() ); + pImpl->xRecorder = SfxRequest::GetMacroRecorder(pImpl->pViewFrame); + if (pImpl->xRecorder) + pImpl->xTransform = util::URLTransformer::create(comphelper::getProcessComponentContext()); + pImpl->aTarget = pImpl->pShell->GetName(); + } + else + { + SAL_WARN("sfx", "Recording unsupported slot: " << pImpl->pPool->GetSlotId(nSlot)); + } +} + + +SfxRequest::SfxRequest +( + SfxViewFrame* pViewFrame, + sal_uInt16 nSlotId + +) + +/* [Description] + + With this constructor events can subsequently be recorded that are not run + across SfxDispatcher (eg from KeyInput() or mouse events). For this, a + SfxRequest instance is created by this constructor and then proceed + exactly as with a SfxRequest that in a <Slot-Execute-Method> is given as a + parameter. +*/ + +: nSlot(nSlotId), + pImpl( new SfxRequest_Impl(this) ) +{ + pImpl->bDone = false; + pImpl->bIgnored = false; + pImpl->SetPool( &pViewFrame->GetPool() ); + pImpl->pShell = nullptr; + pImpl->pSlot = nullptr; + pImpl->nCallMode = SfxCallMode::SYNCHRON; + pImpl->pViewFrame = pViewFrame; + if( pImpl->pViewFrame->GetDispatcher()->GetShellAndSlot_Impl( nSlotId, &pImpl->pShell, &pImpl->pSlot, true, true ) ) + { + pImpl->SetPool( &pImpl->pShell->GetPool() ); + pImpl->xRecorder = SfxRequest::GetMacroRecorder( pViewFrame ); + if (pImpl->xRecorder) + pImpl->xTransform = util::URLTransformer::create(comphelper::getProcessComponentContext()); + pImpl->aTarget = pImpl->pShell->GetName(); + } + else + { + SAL_WARN( "sfx", "Recording unsupported slot: " << pImpl->pPool->GetSlotId(nSlotId) ); + } +} + + +SfxRequest::SfxRequest +( + sal_uInt16 nSlotId, // executed <Slot-Id> + SfxCallMode nMode, // Synch/API/... + SfxItemPool& rPool // necessary for the SfxItemSet for parameters +) + +// creates a SfxRequest without arguments + +: nSlot(nSlotId), + pImpl( new SfxRequest_Impl(this) ) +{ + pImpl->bDone = false; + pImpl->bIgnored = false; + pImpl->SetPool( &rPool ); + pImpl->pShell = nullptr; + pImpl->pSlot = nullptr; + pImpl->nCallMode = nMode; +} + +SfxRequest::SfxRequest +( + const SfxSlot* pSlot, // executed <Slot-Id> + const css::uno::Sequence < css::beans::PropertyValue >& rArgs, + SfxCallMode nMode, // Synch/API/... + SfxItemPool& rPool // necessary for the SfxItemSet for parameters +) +: nSlot(pSlot->GetSlotId()), + pArgs(new SfxAllItemSet(rPool)), + pImpl( new SfxRequest_Impl(this) ) +{ + pImpl->bDone = false; + pImpl->bIgnored = false; + pImpl->SetPool( &rPool ); + pImpl->pShell = nullptr; + pImpl->pSlot = nullptr; + pImpl->nCallMode = nMode; + TransformParameters( nSlot, rArgs, *pArgs, pSlot ); +} + + +SfxRequest::SfxRequest +( + sal_uInt16 nSlotId, + SfxCallMode nMode, + const SfxAllItemSet& rSfxArgs +) + +// creates a SfxRequest with arguments + +: nSlot(nSlotId), + pArgs(new SfxAllItemSet(rSfxArgs)), + pImpl( new SfxRequest_Impl(this) ) +{ + pImpl->bDone = false; + pImpl->bIgnored = false; + pImpl->SetPool( rSfxArgs.GetPool() ); + pImpl->pShell = nullptr; + pImpl->pSlot = nullptr; + pImpl->nCallMode = nMode; +} + + +SfxRequest::SfxRequest +( + sal_uInt16 nSlotId, + SfxCallMode nMode, + const SfxAllItemSet& rSfxArgs, + const SfxAllItemSet& rSfxInternalArgs +) +: SfxRequest(nSlotId, nMode, rSfxArgs) +{ + SetInternalArgs_Impl(rSfxInternalArgs); +} + +SfxCallMode SfxRequest::GetCallMode() const +{ + return pImpl->nCallMode; +} + + +bool SfxRequest::IsSynchronCall() const +{ + return SfxCallMode::SYNCHRON == ( SfxCallMode::SYNCHRON & pImpl->nCallMode ); +} + + +void SfxRequest::SetSynchronCall( bool bSynchron ) +{ + if ( bSynchron ) + pImpl->nCallMode |= SfxCallMode::SYNCHRON; + else + pImpl->nCallMode &= ~SfxCallMode::SYNCHRON; +} + +void SfxRequest::SetInternalArgs_Impl( const SfxAllItemSet& rArgs ) +{ + pImpl->pInternalArgs.reset( new SfxAllItemSet( rArgs ) ); +} + +const SfxItemSet* SfxRequest::GetInternalArgs_Impl() const +{ + return pImpl->pInternalArgs.get(); +} + + +void SfxRequest_Impl::Record +( + const uno::Sequence < beans::PropertyValue >& rArgs // current Parameter +) + +/* [Description] + + Internal helper method to create a repeatable description of the just + executed SfxRequest. +*/ + +{ + if(!xRecorder.is()) + return; + + OUString aCmd = ".uno:" + OUString::createFromAscii( pSlot->GetUnoName() ); + + uno::Reference< container::XIndexReplace > xReplace( xRecorder, uno::UNO_QUERY ); + if ( xReplace.is() && aCmd == ".uno:InsertText" ) + { + sal_Int32 nCount = xReplace->getCount(); + if ( nCount ) + { + frame::DispatchStatement aStatement; + uno::Any aElement = xReplace->getByIndex(nCount-1); + if ( (aElement >>= aStatement) && aStatement.aCommand == aCmd ) + { + OUString aStr; + OUString aNew; + aStatement.aArgs[0].Value >>= aStr; + rArgs[0].Value >>= aNew; + aStr += aNew; + aStatement.aArgs.getArray()[0].Value <<= aStr; + aElement <<= aStatement; + xReplace->replaceByIndex( nCount-1, aElement ); + return; + } + } + } + + css::util::URL aURL; + aURL.Complete = aCmd; + xTransform->parseStrict(aURL); + + if (bDone) + xRecorder->recordDispatch(aURL,rArgs); + else + xRecorder->recordDispatchAsComment(aURL,rArgs); +} + + +void SfxRequest::Record_Impl +( + SfxShell& rSh, // the <SfxShell>, which has executed the Request + const SfxSlot& rSlot, // the <SfxSlot>, which has executed the Request + const css::uno::Reference< css::frame::XDispatchRecorder >& xRecorder, + SfxViewFrame* pViewFrame +) + +/* [Description] + + This internal method marks the specified SfxMakro SfxRequest as recorded in + SfxMakro. Pointer to the parameters in Done() is used again, thus has to + still be alive. +*/ + +{ + pImpl->pShell = &rSh; + pImpl->pSlot = &rSlot; + pImpl->xRecorder = xRecorder; + if (pImpl->xRecorder && !pImpl->xTransform) + pImpl->xTransform = util::URLTransformer::create(comphelper::getProcessComponentContext()); + pImpl->aTarget = rSh.GetName(); + pImpl->pViewFrame = pViewFrame; +} + + +void SfxRequest::SetArgs( const SfxAllItemSet& rArgs ) +{ + pArgs.reset(new SfxAllItemSet(rArgs)); + pImpl->SetPool( pArgs->GetPool() ); +} + + +void SfxRequest::AppendItem(const SfxPoolItem &rItem) +{ + if(!pArgs) + pArgs.reset( new SfxAllItemSet(*pImpl->pPool) ); + pArgs->Put(rItem, rItem.Which()); +} + + +void SfxRequest::RemoveItem( sal_uInt16 nID ) +{ + if (pArgs) + { + pArgs->ClearItem(nID); + if ( !pArgs->Count() ) + pArgs.reset(); + } +} + +void SfxRequest::SetReturnValue(const SfxPoolItem &rItem) +{ + DBG_ASSERT(!pImpl->pRetVal, "Set Return value multiple times?"); + pImpl->pRetVal.reset(rItem.Clone()); +} + + +const SfxPoolItem* SfxRequest::GetReturnValue() const +{ + return pImpl->pRetVal.get(); +} + + +void SfxRequest::Done +( + const SfxItemSet& rSet /* parameters passed on by the application, + that for example were asked for by the user + in a dialogue, 0 if no parameters have been + set */ +) + +/* [Description] + + This method must be called in the <Execute-Method> of the <SfxSlot>s, which + has performed the SfxRequest when the execution actually took place. If + 'Done()' is not called, then the SfxRequest is considered canceled. + + Any return values are passed only when 'Done()' was called. Similar, when + recording a macro only true statements are generated if 'Done()' was + called; for SfxRequests that were not identified as such will instead + be commented out by inserting ('rem'). + + [Note] + + 'Done ()' is not called, for example when a dialog started by the function + was canceled by the user or if the execution could not be performed due to + a wrong context (without use of separate <SfxShell>s). 'Done ()' will be + launched, when executing the function led to a regular error + (for example, file could not be opened). +*/ + +{ + Done_Impl( &rSet ); + + // Keep items if possible, so they can be queried by StarDraw. + if ( !pArgs ) + { + pArgs.reset( new SfxAllItemSet( rSet ) ); + pImpl->SetPool( pArgs->GetPool() ); + } + else + { + SfxItemIter aIter(rSet); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if(!IsInvalidItem(pItem)) + pArgs->Put(*pItem,pItem->Which()); + } + } +} + + +void SfxRequest::Done( bool bRelease ) +// [<SfxRequest::Done(SfxItemSet&)>] +{ + Done_Impl( pArgs.get() ); + if( bRelease ) + pArgs.reset(); +} + + +void SfxRequest::ForgetAllArgs() +{ + pArgs.reset(); + pImpl->pInternalArgs.reset(); +} + + +bool SfxRequest::IsCancelled() const +{ + return pImpl->bCancelled; +} + + +void SfxRequest::Cancel() + +/* [Description] + + Marks this request as no longer executable. For example, if called when + the target (more precisely, its pool) dies. +*/ + +{ + pImpl->bCancelled = true; + pImpl->SetPool( nullptr ); + pArgs.reset(); +} + + +void SfxRequest::Ignore() + +/* [Description] + + If this method is called instead of <SfxRequest::Done()>, then this + request is not recorded. + + [Example] + + The selecting of tools in StarDraw should not be recorded, but the same + slots are to be used from the generation of the tools to the generated + objects. Thus can NoRecords not be specified, i.e. should not be recorded. +*/ + +{ + // Mark as actually executed + pImpl->bIgnored = true; +} + + +void SfxRequest::Done_Impl +( + const SfxItemSet* pSet /* parameters passed on by the application, + that for example were asked for by the user + in a dialogue, 0 if no parameters have been + set */ + +) + +/* [Description] + + Internal method to mark SfxRequest with 'done' and to evaluate the + parameters in 'pSet' in case it is recorded. +*/ + +{ + // Mark as actually executed + pImpl->bDone = true; + + // not Recording + if ( !pImpl->xRecorder.is() ) + return; + + // was running a different slot than requested (Delegation) + if ( nSlot != pImpl->pSlot->GetSlotId() ) + { + // Search Slot again + pImpl->pSlot = pImpl->pShell->GetInterface()->GetSlot(nSlot); + DBG_ASSERT( pImpl->pSlot, "delegated SlotId not found" ); + if ( !pImpl->pSlot ) // playing it safe + return; + } + + // recordable? + // new Recording uses UnoName! + SAL_WARN_IF( !pImpl->pSlot->pUnoName, "sfx", "Recording not exported slot: " + << pImpl->pSlot->GetSlotId() ); + + if ( !pImpl->pSlot->pUnoName ) // playing it safe + return; + + // often required values + SfxItemPool &rPool = pImpl->pShell->GetPool(); + + // Property-Slot? + if ( !pImpl->pSlot->IsMode(SfxSlotMode::METHOD) ) + { + // get the property as SfxPoolItem + const SfxPoolItem *pItem(nullptr); + const sal_uInt16 nWhich(rPool.GetWhich(pImpl->pSlot->GetSlotId())); + const bool bItemStateSet(nullptr != pSet); + const SfxItemState eState(bItemStateSet ? pSet->GetItemState( nWhich, false, &pItem ) : SfxItemState::DEFAULT); + SAL_WARN_IF( !bItemStateSet || SfxItemState::SET != eState, "sfx", "Recording property not available: " + << pImpl->pSlot->GetSlotId() ); + uno::Sequence < beans::PropertyValue > aSeq; + + if ( bItemStateSet && SfxItemState::SET == eState ) + TransformItems( pImpl->pSlot->GetSlotId(), *pSet, aSeq, pImpl->pSlot ); + + pImpl->Record( aSeq ); + } + + // record everything in a single statement? + else if ( pImpl->pSlot->IsMode(SfxSlotMode::RECORDPERSET) ) + { + uno::Sequence < beans::PropertyValue > aSeq; + if ( pSet ) + TransformItems( pImpl->pSlot->GetSlotId(), *pSet, aSeq, pImpl->pSlot ); + pImpl->Record( aSeq ); + } + + // record each item as a single statement + else if ( pImpl->pSlot->IsMode(SfxSlotMode::RECORDPERITEM) ) + { + if ( pSet ) + { + // iterate over Items + SfxItemIter aIter(*pSet); + for ( const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem() ) + { + // to determine the slot ID for the individual item + sal_uInt16 nSlotId = rPool.GetSlotId( pItem->Which() ); + if ( nSlotId == nSlot ) + { + // play it safe; repair the wrong flags + OSL_FAIL( "recursion RecordPerItem - use RecordPerSet!" ); + SfxSlot *pSlot = const_cast<SfxSlot*>(pImpl->pSlot); + pSlot->nFlags &= ~SfxSlotMode::RECORDPERITEM; + pSlot->nFlags &= SfxSlotMode::RECORDPERSET; + } + + // Record a Sub-Request + SfxRequest aReq( pImpl->pViewFrame, nSlotId ); + if ( aReq.pImpl->pSlot ) + aReq.AppendItem( *pItem ); + aReq.Done(); + } + } + else + { + //HACK(think about this again) + pImpl->Record( uno::Sequence < beans::PropertyValue >() ); + } + } +} + + +bool SfxRequest::IsDone() const + +/* [Description] + + With this method it can be queried whether the SfxRequest was actually + executed or not. If a SfxRequest was not executed, then this is for example + because it was canceled by the user or the context for this request was + wrong, this was not implemented on a separate <SfxShell>. + + SfxRequest instances that return false will not be recorded. + + [Cross-reference] + + <SfxRequest::Done(const SfxItemSet&)> + <SfxRequest::Done()> +*/ + +{ + return pImpl->bDone; +} + + +css::uno::Reference< css::frame::XDispatchRecorder > SfxRequest::GetMacroRecorder( SfxViewFrame const * pView ) + +/* [Description] + + This recorder is an attempt for dispatch () to get calls from the Frame. + This is then available through a property by a supplier but only when + recording was turned on. + + (See also SfxViewFrame::MiscExec_Impl() and SID_RECORDING) +*/ + +{ + css::uno::Reference< css::frame::XDispatchRecorder > xRecorder; + + if (!pView) + pView = SfxViewFrame::Current(); + + if (!pView) + return xRecorder; + + css::uno::Reference< css::beans::XPropertySet > xSet( + pView->GetFrame().GetFrameInterface(), + css::uno::UNO_QUERY); + + if(xSet.is()) + { + css::uno::Any aProp = xSet->getPropertyValue("DispatchRecorderSupplier"); + css::uno::Reference< css::frame::XDispatchRecorderSupplier > xSupplier; + aProp >>= xSupplier; + if(xSupplier.is()) + xRecorder = xSupplier->getDispatchRecorder(); + } + + return xRecorder; +} + +bool SfxRequest::HasMacroRecorder( SfxViewFrame const * pView ) +{ + return GetMacroRecorder( pView ).is(); +} + + +bool SfxRequest::IsAPI() const + +/* [Description] + + Returns true if this SfxRequest was generated by an API (for example BASIC), + otherwise false. +*/ + +{ + return SfxCallMode::API == ( SfxCallMode::API & pImpl->nCallMode ); +} + + +void SfxRequest::SetModifier( sal_uInt16 nModi ) +{ + pImpl->nModifier = nModi; +} + + +sal_uInt16 SfxRequest::GetModifier() const +{ + return pImpl->nModifier; +} + + +void SfxRequest::AllowRecording( bool bSet ) +{ + pImpl->bAllowRecording = bSet; +} + +bool SfxRequest::AllowsRecording() const +{ + bool bAllow = pImpl->bAllowRecording; + if( !bAllow ) + bAllow = ( SfxCallMode::API != ( SfxCallMode::API & pImpl->nCallMode ) ) && + ( SfxCallMode::RECORD == ( SfxCallMode::RECORD & pImpl->nCallMode ) ); + return bAllow; +} + +void SfxRequest::ReleaseArgs() +{ + pArgs.reset(); + pImpl->pInternalArgs.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/sfxstatuslistener.cxx b/sfx2/source/control/sfxstatuslistener.cxx new file mode 100644 index 000000000..5ee3d471f --- /dev/null +++ b/sfx2/source/control/sfxstatuslistener.cxx @@ -0,0 +1,219 @@ +/* -*- 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 <sfx2/sfxstatuslistener.hxx> +#include <svl/poolitem.hxx> +#include <svl/eitem.hxx> +#include <svl/stritem.hxx> +#include <svl/intitem.hxx> +#include <svl/visitem.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <vcl/svapp.hxx> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/frame/status/ItemStatus.hpp> +#include <com/sun/star/frame/status/Visibility.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> + +#include <sfx2/viewfrm.hxx> +#include <sfx2/dispatch.hxx> +#include <unoctitm.hxx> +#include <sfx2/msgpool.hxx> +#include <sfx2/msg.hxx> + +using namespace ::cppu; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::frame::status; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::util; + +SfxStatusListener::SfxStatusListener( const Reference< XDispatchProvider >& rDispatchProvider, sal_uInt16 nSlotId, const OUString& rCommand ) : + m_nSlotID( nSlotId ), + m_xDispatchProvider( rDispatchProvider ) +{ + m_aCommand.Complete = rCommand; + Reference< XURLTransformer > xTrans( URLTransformer::create( ::comphelper::getProcessComponentContext() ) ); + xTrans->parseStrict( m_aCommand ); + if ( rDispatchProvider.is() ) + m_xDispatch = rDispatchProvider->queryDispatch( m_aCommand, OUString(), 0 ); +} + +SfxStatusListener::~SfxStatusListener() +{ +} + +// old sfx controller item C++ API +void SfxStatusListener::StateChangedAtStatusListener( SfxItemState, const SfxPoolItem* ) +{ + // must be implemented by sub class +} + +void SfxStatusListener::UnBind() +{ + if ( m_xDispatch.is() ) + { + Reference< XStatusListener > aStatusListener(this); + m_xDispatch->removeStatusListener( aStatusListener, m_aCommand ); + m_xDispatch.clear(); + } +} + +void SfxStatusListener::ReBind() +{ + Reference< XStatusListener > aStatusListener(this); + if ( m_xDispatch.is() ) + m_xDispatch->removeStatusListener( aStatusListener, m_aCommand ); + if ( m_xDispatchProvider.is() ) + { + try + { + m_xDispatch = m_xDispatchProvider->queryDispatch( m_aCommand, OUString(), 0 ); + if ( m_xDispatch.is() ) + m_xDispatch->addStatusListener( aStatusListener, m_aCommand ); + } + catch( Exception& ) + { + } + } +} + +// new UNO API +void SAL_CALL SfxStatusListener::dispose() +{ + if ( m_xDispatch.is() && !m_aCommand.Complete.isEmpty() ) + { + try + { + Reference< XStatusListener > aStatusListener(this); + m_xDispatch->removeStatusListener( aStatusListener, m_aCommand ); + } + catch ( Exception& ) + { + } + } + + m_xDispatch.clear(); + m_xDispatchProvider.clear(); +} + +void SAL_CALL SfxStatusListener::addEventListener( const Reference< XEventListener >& ) +{ + // do nothing - this is a wrapper class which does not support listeners +} + +void SAL_CALL SfxStatusListener::removeEventListener( const Reference< XEventListener >& ) +{ + // do nothing - this is a wrapper class which does not support listeners +} + +void SAL_CALL SfxStatusListener::disposing( const EventObject& Source ) +{ + SolarMutexGuard aGuard; + + if ( Source.Source == Reference< XInterface >( m_xDispatch, UNO_QUERY )) + m_xDispatch.clear(); + else if ( Source.Source == Reference< XInterface >( m_xDispatchProvider, UNO_QUERY )) + m_xDispatchProvider.clear(); +} + +void SAL_CALL SfxStatusListener::statusChanged( const FeatureStateEvent& rEvent) +{ + SolarMutexGuard aGuard; + + SfxViewFrame* pViewFrame = nullptr; + if ( m_xDispatch.is() ) + { + Reference< XUnoTunnel > xTunnel( m_xDispatch, UNO_QUERY ); + if (auto pDisp = comphelper::getFromUnoTunnel<SfxOfficeDispatch>(xTunnel)) + pViewFrame = pDisp->GetDispatcher_Impl()->GetFrame(); + } + + SfxSlotPool& rPool = SfxSlotPool::GetSlotPool( pViewFrame ); + const SfxSlot* pSlot = rPool.GetSlot( m_nSlotID ); + + SfxItemState eState = SfxItemState::DISABLED; + std::unique_ptr<SfxPoolItem> pItem; + if ( rEvent.IsEnabled ) + { + eState = SfxItemState::DEFAULT; + css::uno::Type aType = rEvent.State.getValueType(); + + if ( aType == ::cppu::UnoType<void>::get() ) + { + pItem.reset(new SfxVoidItem( m_nSlotID )); + eState = SfxItemState::UNKNOWN; + } + else if ( aType == cppu::UnoType< bool >::get() ) + { + bool bTemp = false; + rEvent.State >>= bTemp ; + pItem.reset(new SfxBoolItem( m_nSlotID, bTemp )); + } + else if ( aType == cppu::UnoType< ::cppu::UnoUnsignedShortType >::get() ) + { + sal_uInt16 nTemp = 0; + rEvent.State >>= nTemp ; + pItem.reset(new SfxUInt16Item( m_nSlotID, nTemp )); + } + else if ( aType == cppu::UnoType<sal_uInt32>::get() ) + { + sal_uInt32 nTemp = 0; + rEvent.State >>= nTemp ; + pItem.reset(new SfxUInt32Item( m_nSlotID, nTemp )); + } + else if ( aType == cppu::UnoType<OUString>::get() ) + { + OUString sTemp ; + rEvent.State >>= sTemp ; + pItem.reset(new SfxStringItem( m_nSlotID, sTemp )); + } + else if ( aType == cppu::UnoType< css::frame::status::ItemStatus >::get() ) + { + ItemStatus aItemStatus; + rEvent.State >>= aItemStatus; + eState = static_cast<SfxItemState>(aItemStatus.State); + pItem.reset(new SfxVoidItem( m_nSlotID )); + } + else if ( aType == cppu::UnoType< css::frame::status::Visibility >::get() ) + { + Visibility aVisibilityStatus; + rEvent.State >>= aVisibilityStatus; + pItem.reset(new SfxVisibilityItem( m_nSlotID, aVisibilityStatus.bVisible )); + } + else + { + if ( pSlot ) + pItem = pSlot->GetType()->CreateItem(); + if ( pItem ) + { + pItem->SetWhich( m_nSlotID ); + pItem->PutValue( rEvent.State, 0 ); + } + else + pItem.reset(new SfxVoidItem( m_nSlotID )); + } + } + + StateChangedAtStatusListener( eState, pItem.get() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/shell.cxx b/sfx2/source/control/shell.cxx new file mode 100644 index 000000000..ab3f7e869 --- /dev/null +++ b/sfx2/source/control/shell.cxx @@ -0,0 +1,743 @@ +/* -*- 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 <com/sun/star/embed/VerbDescriptor.hpp> +#include <com/sun/star/embed/VerbAttributes.hpp> +#include <officecfg/Office/Common.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <svl/itempool.hxx> +#include <svl/setitem.hxx> +#include <svl/undo.hxx> +#include <itemdel.hxx> +#include <svtools/asynclink.hxx> +#include <unotools/configmgr.hxx> +#include <comphelper/lok.hxx> +#include <sfx2/shell.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxsids.hrc> +#include <statcach.hxx> +#include <sidebar/ContextChangeBroadcaster.hxx> +#include <com/sun/star/ui/dialogs/XSLTFilterDialog.hpp> +#include <tools/debug.hxx> + +#include <memory> +#include <vector> +#include <map> + +#include <desktop/crashreport.hxx> + +using namespace com::sun::star; + +struct SfxShell_Impl: public SfxBroadcaster +{ + OUString aObjectName; // Name of Sbx-Objects + // Maps the Which() field to a pointer to a SfxPoolItem + std::map<sal_uInt16, std::unique_ptr<SfxPoolItem>> + m_Items; // Data exchange on Item level + SfxViewShell* pViewSh; // SfxViewShell if Shell is + // ViewFrame/ViewShell/SubShell list + SfxViewFrame* pFrame; // Frame, if <UI-active> + SfxRepeatTarget* pRepeatTarget; // SbxObjectRef xParent; + bool bActive; + SfxDisableFlags nDisableFlags; + std::unique_ptr<svtools::AsynchronLink> pExecuter; + std::unique_ptr<svtools::AsynchronLink> pUpdater; + std::vector<std::unique_ptr<SfxSlot> > aSlotArr; + + css::uno::Sequence < css::embed::VerbDescriptor > aVerbList; + ::sfx2::sidebar::ContextChangeBroadcaster maContextChangeBroadcaster; + + SfxShell_Impl() + : pViewSh(nullptr) + , pFrame(nullptr) + , pRepeatTarget(nullptr) + , bActive(false) + , nDisableFlags(SfxDisableFlags::NONE) + { + } + + virtual ~SfxShell_Impl() override { pExecuter.reset(); pUpdater.reset();} +}; + + +void SfxShell::EmptyExecStub(SfxShell *, SfxRequest &) +{ +} + +void SfxShell::EmptyStateStub(SfxShell *, SfxItemSet &) +{ +} + +SfxShell::SfxShell() +: pImpl(new SfxShell_Impl), + pPool(nullptr), + pUndoMgr(nullptr) +{ +} + +SfxShell::SfxShell( SfxViewShell *pViewSh ) +: pImpl(new SfxShell_Impl), + pPool(nullptr), + pUndoMgr(nullptr) +{ + pImpl->pViewSh = pViewSh; +} + +SfxShell::~SfxShell() +{ +} + +void SfxShell::SetName( const OUString &rName ) +{ + pImpl->aObjectName = rName; +} + +const OUString& SfxShell::GetName() const +{ + return pImpl->aObjectName; +} + +SfxDispatcher* SfxShell::GetDispatcher() const +{ + return pImpl->pFrame ? pImpl->pFrame->GetDispatcher() : nullptr; +} + +SfxViewShell* SfxShell::GetViewShell() const +{ + return pImpl->pViewSh; +} + +SfxViewFrame* SfxShell::GetFrame() const +{ + if ( pImpl->pFrame ) + return pImpl->pFrame; + if ( pImpl->pViewSh ) + return pImpl->pViewSh->GetViewFrame(); + return nullptr; +} + +const SfxPoolItem* SfxShell::GetItem +( + sal_uInt16 nSlotId // Slot-Id of the querying <SfxPoolItem>s +) const +{ + auto const it = pImpl->m_Items.find( nSlotId ); + if (it != pImpl->m_Items.end()) + return it->second.get(); + return nullptr; +} + +void SfxShell::PutItem +( + const SfxPoolItem& rItem /* Instance, of which a copy is created, + which is stored in the SfxShell in a list. */ +) +{ + DBG_ASSERT( dynamic_cast< const SfxSetItem* >( &rItem) == nullptr, "SetItems aren't allowed here" ); + DBG_ASSERT( SfxItemPool::IsSlot( rItem.Which() ), + "items with Which-Ids aren't allowed here" ); + + // MSC made a mess here of WNT/W95, beware of changes + SfxPoolItem *pItem = rItem.Clone(); + SfxPoolItemHint aItemHint( pItem ); + sal_uInt16 nWhich = rItem.Which(); + + auto const it = pImpl->m_Items.find(nWhich); + if (it != pImpl->m_Items.end()) + { + // Replace Item + pImpl->m_Items.erase( it ); + pImpl->m_Items.insert(std::make_pair(nWhich, std::unique_ptr<SfxPoolItem>(pItem))); + + // if active, notify Bindings + SfxDispatcher *pDispat = GetDispatcher(); + if ( pDispat ) + { + SfxBindings* pBindings = pDispat->GetBindings(); + pBindings->Broadcast( aItemHint ); + sal_uInt16 nSlotId = nWhich; //pItem->GetSlotId(); + SfxStateCache* pCache = pBindings->GetStateCache( nSlotId ); + if ( pCache ) + { + pCache->SetState( SfxItemState::DEFAULT, pItem, true ); + pCache->SetCachedState( true ); + } + } + return; + } + else + { + Broadcast( aItemHint ); + pImpl->m_Items.insert(std::make_pair(nWhich, std::unique_ptr<SfxPoolItem>(pItem))); + } +} + +SfxInterface* SfxShell::GetInterface() const +{ + return GetStaticInterface(); +} + +SfxUndoManager* SfxShell::GetUndoManager() +{ + return pUndoMgr; +} + +void SfxShell::SetUndoManager( SfxUndoManager *pNewUndoMgr ) +{ + OSL_ENSURE( ( pUndoMgr == nullptr ) || ( pNewUndoMgr == nullptr ) || ( pUndoMgr == pNewUndoMgr ), + "SfxShell::SetUndoManager: exchanging one non-NULL manager with another non-NULL manager? Suspicious!" ); + // there's at least one client of our UndoManager - the DocumentUndoManager at the SfxBaseModel - which + // caches the UndoManager, and registers itself as listener. If exchanging non-NULL UndoManagers is really + // a supported scenario (/me thinks it is not), then we would need to notify all such clients instances. + + pUndoMgr = pNewUndoMgr; + if (pUndoMgr && !utl::ConfigManager::IsFuzzing()) + { + pUndoMgr->SetMaxUndoActionCount( + officecfg::Office::Common::Undo::Steps::get()); + } +} + +SfxRepeatTarget* SfxShell::GetRepeatTarget() const +{ + return pImpl->pRepeatTarget; +} + +void SfxShell::SetRepeatTarget( SfxRepeatTarget *pTarget ) +{ + pImpl->pRepeatTarget = pTarget; +} + +void SfxShell::Invalidate +( + sal_uInt16 nId /* Invalidated Slot-Id or Which-Id. + If these are 0 (default), then all + by this Shell currently handled Slot-Ids are + invalidated. */ +) +{ + if ( !GetViewShell() ) + { + OSL_FAIL( "wrong Invalidate method called!" ); + return; + } + + Invalidate_Impl( GetViewShell()->GetViewFrame()->GetBindings(), nId ); +} + +void SfxShell::Invalidate_Impl( SfxBindings& rBindings, sal_uInt16 nId ) +{ + if ( nId == 0 ) + { + rBindings.InvalidateShell( *this ); + } + else + { + const SfxInterface *pIF = GetInterface(); + do + { + const SfxSlot *pSlot = pIF->GetSlot(nId); + if ( pSlot ) + { + // Invalidate the Slot itself + rBindings.Invalidate( pSlot->GetSlotId() ); + return; + } + + pIF = pIF->GetGenoType(); + } + + while ( pIF ); + + SAL_INFO( "sfx.control", "W3: invalidating slot-id unknown in shell" ); + } +} + +void SfxShell::HandleOpenXmlFilterSettings(SfxRequest & rReq) +{ + try + { + uno::Reference < ui::dialogs::XExecutableDialog > xDialog = ui::dialogs::XSLTFilterDialog::create( ::comphelper::getProcessComponentContext() ); + xDialog->execute(); + } + catch (const uno::Exception&) + { + } + rReq.Ignore (); +} + +void SfxShell::DoActivate_Impl( SfxViewFrame *pFrame, bool bMDI ) +{ + SfxObjectShell* pObjectShell = GetObjectShell(); + if ( pObjectShell ) + { + const OUString sActiveDocName = pObjectShell->GetTitle(); + if( !pImpl->aObjectName.startsWith(sActiveDocName) ) + { + CrashReporter::setActiveSfxObjectName(pImpl->aObjectName); + } + } + else + { + CrashReporter::setActiveSfxObjectName(pImpl->aObjectName); + } + +#ifdef DBG_UTIL + const SfxInterface *p_IF = GetInterface(); + if ( !p_IF ) + return; +#endif + SAL_INFO( + "sfx.control", + "SfxShell::DoActivate() " << this << " " << GetInterface()->GetClassName() + << " bMDI " << (bMDI ? "MDI" : "")); + + if ( bMDI ) + { + // Remember Frame, in which it was activated + pImpl->pFrame = pFrame; + pImpl->bActive = true; + } + + // Notify Subclass + Activate(bMDI); +} + +void SfxShell::DoDeactivate_Impl( SfxViewFrame const *pFrame, bool bMDI ) +{ +#ifdef DBG_UTIL + const SfxInterface *p_IF = GetInterface(); + if ( !p_IF ) + return; +#endif + SAL_INFO( + "sfx.control", + "SfxShell::DoDeactivate()" << this << " " << GetInterface()->GetClassName() + << " bMDI " << (bMDI ? "MDI" : "")); + + // Only when it comes from a Frame + // (not when for instance by popping BASIC-IDE from AppDisp) + if ( bMDI && pImpl->pFrame == pFrame ) + { + // deliver + pImpl->pFrame = nullptr; + pImpl->bActive = false; + } + + // Notify Subclass + Deactivate(bMDI); +} + +bool SfxShell::IsActive() const +{ + return pImpl->bActive; +} + +void SfxShell::Activate +( + bool /*bMDI*/ /* TRUE + the <SfxDispatcher>, on which the SfxShell is + located, is activated or the SfxShell instance + was pushed on an active SfxDispatcher. + (compare with SystemWindow::IsMDIActivate()) + + FALSE + the <SfxViewFrame>, on which SfxDispatcher + the SfxShell instance is located, was + activated. (for example by a closing dialog) */ +) +{ + BroadcastContextForActivation(true); +} + +void SfxShell::Deactivate +( + bool /*bMDI*/ /* TRUE + the <SfxDispatcher>, on which the SfxShell is + located, is inactivated or the SfxShell instance + was popped on an active SfxDispatcher. + (compare with SystemWindow::IsMDIActivate()) + + FALSE + the <SfxViewFrame>, on which SfxDispatcher + the SfxShell instance is located, was + deactivated. (for example by a dialog) */ +) +{ + BroadcastContextForActivation(false); +} + +bool SfxShell::CanExecuteSlot_Impl( const SfxSlot &rSlot ) +{ + // Get Slot status + SfxItemPool &rPool = GetPool(); + const sal_uInt16 nId = rSlot.GetWhich( rPool ); + SfxItemSet aSet(rPool, nId, nId); + SfxStateFunc pFunc = rSlot.GetStateFnc(); + (*pFunc)( this, aSet ); + return aSet.GetItemState(nId) != SfxItemState::DISABLED; +} + +bool SfxShell::IsConditionalFastCall( const SfxRequest &rReq ) +{ + sal_uInt16 nId = rReq.GetSlot(); + bool bRet = false; + + if (nId == SID_UNDO || nId == SID_REDO) + { + const SfxItemSet* pArgs = rReq.GetArgs(); + if (pArgs && pArgs->HasItem(SID_REPAIRPACKAGE)) + bRet = true; + } + return bRet; +} + + +static void ShellCall_Impl( void* pObj, void* pArg ) +{ + static_cast<SfxShell*>(pObj)->ExecuteSlot( *static_cast<SfxRequest*>(pArg) ); +} + +void SfxShell::ExecuteSlot( SfxRequest& rReq, bool bAsync ) +{ + if( !bAsync ) + ExecuteSlot( rReq ); + else + { + if( !pImpl->pExecuter ) + pImpl->pExecuter.reset( new svtools::AsynchronLink( + Link<void*,void>( this, ShellCall_Impl ) ) ); + pImpl->pExecuter->Call( new SfxRequest( rReq ) ); + } +} + +const SfxPoolItem* SfxShell::ExecuteSlot +( + SfxRequest &rReq, // the relayed <SfxRequest> + const SfxInterface* pIF // default = 0 means get virtually +) +{ + if ( !pIF ) + pIF = GetInterface(); + + sal_uInt16 nSlot = rReq.GetSlot(); + const SfxSlot* pSlot = nullptr; + if ( nSlot >= SID_VERB_START && nSlot <= SID_VERB_END ) + pSlot = GetVerbSlot_Impl(nSlot); + if ( !pSlot ) + pSlot = pIF->GetSlot(nSlot); + DBG_ASSERT( pSlot, "slot not supported" ); + + SfxExecFunc pFunc = pSlot->GetExecFnc(); + if ( pFunc ) + (*pFunc)( this, rReq ); + + return rReq.GetReturnValue(); +} + +const SfxPoolItem* SfxShell::GetSlotState +( + sal_uInt16 nSlotId, // Slot-Id to the Slots in question + const SfxInterface* pIF, // default = 0 means get virtually + SfxItemSet* pStateSet // SfxItemSet of the Slot-State method +) +{ + // Get Slot on the given Interface + if ( !pIF ) + pIF = GetInterface(); + SfxItemState eState(SfxItemState::DEFAULT); + bool bItemStateSet(false); + SfxItemPool &rPool = GetPool(); + + const SfxSlot* pSlot = nullptr; + if ( nSlotId >= SID_VERB_START && nSlotId <= SID_VERB_END ) + pSlot = GetVerbSlot_Impl(nSlotId); + if ( !pSlot ) + pSlot = pIF->GetSlot(nSlotId); + if ( pSlot ) + // Map on Which-Id if possible + nSlotId = pSlot->GetWhich( rPool ); + + // Get Item and Item status + const SfxPoolItem *pItem = nullptr; + SfxItemSet aSet( rPool, nSlotId, nSlotId ); // else pItem dies too soon + if ( nullptr != pSlot ) + { + // Call Status method + SfxStateFunc pFunc = pSlot->GetStateFnc(); + if ( pFunc ) + (*pFunc)( this, aSet ); + eState = aSet.GetItemState( nSlotId, true, &pItem ); + bItemStateSet = true; + + // get default Item if possible + if ( eState == SfxItemState::DEFAULT ) + { + if ( SfxItemPool::IsWhich(nSlotId) ) + pItem = &rPool.GetDefaultItem(nSlotId); + else + eState = SfxItemState::DONTCARE; + } + } + + // Evaluate Item and item status and possibly maintain them in pStateSet + std::unique_ptr<SfxPoolItem> pRetItem; + if ( !bItemStateSet || eState <= SfxItemState::DISABLED ) + { + if ( pStateSet ) + pStateSet->DisableItem(nSlotId); + return nullptr; + } + else if ( bItemStateSet && eState == SfxItemState::DONTCARE ) + { + if ( pStateSet ) + pStateSet->ClearItem(nSlotId); + pRetItem.reset( new SfxVoidItem(0) ); + } + else // bItemStateSet && eState >= SfxItemState::DEFAULT + { + if ( pStateSet && pStateSet->Put( *pItem ) ) + return &pStateSet->Get( pItem->Which() ); + pRetItem.reset(pItem->Clone()); + } + auto pTemp = pRetItem.get(); + DeleteItemOnIdle(std::move(pRetItem)); + + return pTemp; +} + +static SFX_EXEC_STUB(SfxShell, VerbExec) +static void SfxStubSfxShellVerbState(SfxShell *, SfxItemSet& rSet) +{ + SfxShell::VerbState( rSet ); +} + +void SfxShell::SetVerbs(const css::uno::Sequence < css::embed::VerbDescriptor >& aVerbs) +{ + SfxViewShell *pViewSh = dynamic_cast<SfxViewShell*>( this ); + + DBG_ASSERT(pViewSh, "Only call SetVerbs at the ViewShell!"); + if ( !pViewSh ) + return; + + // First make all Statecaches dirty, so that no-one no longer tries to use + // the Slots + { + SfxBindings *pBindings = + pViewSh->GetViewFrame()->GetDispatcher()->GetBindings(); + sal_uInt16 nCount = pImpl->aSlotArr.size(); + for (sal_uInt16 n1=0; n1<nCount ; n1++) + { + sal_uInt16 nId = SID_VERB_START + n1; + pBindings->Invalidate(nId, false, true); + } + } + + sal_uInt16 nr=0; + for (sal_Int32 n=0; n<aVerbs.getLength(); n++) + { + sal_uInt16 nSlotId = SID_VERB_START + nr++; + DBG_ASSERT(nSlotId <= SID_VERB_END, "Too many Verbs!"); + if (nSlotId > SID_VERB_END) + break; + + SfxSlot *pNewSlot = new SfxSlot; + pNewSlot->nSlotId = nSlotId; + pNewSlot->nGroupId = SfxGroupId::NONE; + + // Verb slots must be executed asynchronously, so that they can be + // destroyed while executing. + pNewSlot->nFlags = SfxSlotMode::ASYNCHRON | SfxSlotMode::CONTAINER; + pNewSlot->nMasterSlotId = 0; + pNewSlot->nValue = 0; + pNewSlot->fnExec = SFX_STUB_PTR(SfxShell,VerbExec); + pNewSlot->fnState = SFX_STUB_PTR(SfxShell,VerbState); + pNewSlot->pType = nullptr; // HACK(SFX_TYPE(SfxVoidItem)) ??? + pNewSlot->nArgDefCount = 0; + pNewSlot->pFirstArgDef = nullptr; + pNewSlot->pUnoName = nullptr; + + if (!pImpl->aSlotArr.empty()) + { + SfxSlot& rSlot = *pImpl->aSlotArr[0]; + pNewSlot->pNextSlot = rSlot.pNextSlot; + rSlot.pNextSlot = pNewSlot; + } + else + pNewSlot->pNextSlot = pNewSlot; + + pImpl->aSlotArr.insert(pImpl->aSlotArr.begin() + static_cast<sal_uInt16>(n), std::unique_ptr<SfxSlot>(pNewSlot)); + } + + pImpl->aVerbList = aVerbs; + + // The status of SID_OBJECT is collected in the controller directly on + // the Shell, it is thus enough to encourage a new status update + SfxBindings* pBindings = pViewSh->GetViewFrame()->GetDispatcher()->GetBindings(); + pBindings->Invalidate(SID_OBJECT, true, true); +} + +const css::uno::Sequence < css::embed::VerbDescriptor >& SfxShell::GetVerbs() const +{ + return pImpl->aVerbList; +} + +void SfxShell::VerbExec(SfxRequest& rReq) +{ + sal_uInt16 nId = rReq.GetSlot(); + SfxViewShell *pViewShell = GetViewShell(); + if ( !pViewShell ) + return; + + bool bReadOnly = pViewShell->GetObjectShell()->IsReadOnly(); + const css::uno::Sequence < css::embed::VerbDescriptor > aList = pViewShell->GetVerbs(); + sal_Int32 nVerb = 0; + for (const auto& rVerb : aList) + { + // check for ReadOnly verbs + if ( bReadOnly && !(rVerb.VerbAttributes & embed::VerbAttributes::MS_VERBATTR_NEVERDIRTIES) ) + continue; + + // check for verbs that shouldn't appear in the menu + if ( !(rVerb.VerbAttributes & embed::VerbAttributes::MS_VERBATTR_ONCONTAINERMENU) ) + continue; + + if (nId == SID_VERB_START + nVerb++) + { + pViewShell->DoVerb(rVerb.VerbID); + rReq.Done(); + return; + } + } +} + +void SfxShell::VerbState(SfxItemSet& ) +{ +} + +const SfxSlot* SfxShell::GetVerbSlot_Impl(sal_uInt16 nId) const +{ + css::uno::Sequence < css::embed::VerbDescriptor > rList = pImpl->aVerbList; + + DBG_ASSERT(nId >= SID_VERB_START && nId <= SID_VERB_END,"Wrong VerbId!"); + sal_uInt16 nIndex = nId - SID_VERB_START; + DBG_ASSERT(nIndex < rList.getLength(),"Wrong VerbId!"); + + if (nIndex < rList.getLength()) + return pImpl->aSlotArr[nIndex].get(); + else + return nullptr; +} + +SfxObjectShell* SfxShell::GetObjectShell() +{ + if ( GetViewShell() ) + return GetViewShell()->GetViewFrame()->GetObjectShell(); + else + return nullptr; +} + +bool SfxShell::HasUIFeature(SfxShellFeature) const +{ + return false; +} + +static void DispatcherUpdate_Impl( void*, void* pArg ) +{ + static_cast<SfxDispatcher*>(pArg)->Update_Impl( true ); + static_cast<SfxDispatcher*>(pArg)->GetBindings()->InvalidateAll(false); +} + +void SfxShell::UIFeatureChanged() +{ + SfxViewFrame *pFrame = GetFrame(); + if ( pFrame && pFrame->IsVisible() ) + { + // Also force an update, if dispatcher is already updated otherwise + // something my get stuck in the bunkered tools. Asynchronous call to + // prevent recursion. + if ( !pImpl->pUpdater ) + pImpl->pUpdater.reset( new svtools::AsynchronLink( Link<void*,void>( this, DispatcherUpdate_Impl ) ) ); + + // Multiple views allowed + pImpl->pUpdater->Call( pFrame->GetDispatcher(), true ); + } +} + +void SfxShell::SetDisableFlags( SfxDisableFlags nFlags ) +{ + pImpl->nDisableFlags = nFlags; +} + +SfxDisableFlags SfxShell::GetDisableFlags() const +{ + return pImpl->nDisableFlags; +} + +std::optional<SfxItemSet> SfxShell::CreateItemSet( sal_uInt16 ) +{ + return {}; +} + +void SfxShell::ApplyItemSet( sal_uInt16, const SfxItemSet& ) +{ +} + +void SfxShell::SetContextName (const OUString& rsContextName) +{ + pImpl->maContextChangeBroadcaster.Initialize(rsContextName); +} + +void SfxShell::SetViewShell_Impl( SfxViewShell* pView ) +{ + pImpl->pViewSh = pView; +} + +void SfxShell::BroadcastContextForActivation (const bool bIsActivated) +{ + // Avoids activation and de-activation (can be seen on switching view) from causing + // the sidebar to re-build. Such switching can happen as we change view to render + // using LOK for example, and is un-necessary for Online. + if (comphelper::LibreOfficeKit::isDialogPainting()) + return; + + SfxViewFrame* pViewFrame = GetFrame(); + if (pViewFrame != nullptr) + { + if (bIsActivated) + pImpl->maContextChangeBroadcaster.Activate(pViewFrame->GetFrame().GetFrameInterface()); + else + pImpl->maContextChangeBroadcaster.Deactivate(pViewFrame->GetFrame().GetFrameInterface()); + } +} + +bool SfxShell::SetContextBroadcasterEnabled (const bool bIsEnabled) +{ + return pImpl->maContextChangeBroadcaster.SetBroadcasterEnabled(bIsEnabled); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/sorgitm.cxx b/sfx2/source/control/sorgitm.cxx new file mode 100644 index 000000000..a9005619a --- /dev/null +++ b/sfx2/source/control/sorgitm.cxx @@ -0,0 +1,85 @@ +/* -*- 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 <sfx2/sfxsids.hrc> +#include <sorgitm.hxx> +#include <osl/diagnose.h> + +SfxPoolItem* SfxScriptOrganizerItem::CreateDefault() { return new SfxScriptOrganizerItem; } + + +SfxScriptOrganizerItem::SfxScriptOrganizerItem() +{ +} + +SfxScriptOrganizerItem* SfxScriptOrganizerItem::Clone( SfxItemPool * ) const +{ + return new SfxScriptOrganizerItem( *this ); +} + +bool SfxScriptOrganizerItem::operator==( const SfxPoolItem& rItem) const +{ + return SfxStringItem::operator==(rItem) && + aLanguage == static_cast<const SfxScriptOrganizerItem &>(rItem).aLanguage; +} + + +bool SfxScriptOrganizerItem::QueryValue( css::uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + OUString aValue; + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case 0: + case MID_SCRIPT_ORGANIZER_LANGUAGE: + aValue = aLanguage; + break; + default: + OSL_FAIL("Wrong MemberId!"); + return false; + } + + rVal <<= aValue; + + return true; +} + +bool SfxScriptOrganizerItem::PutValue( const css::uno::Any& rVal, sal_uInt8 nMemberId ) +{ + OUString aValue; + bool bRet = false; + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case 0: + case MID_SCRIPT_ORGANIZER_LANGUAGE: + bRet = (rVal >>= aValue); + if ( bRet ) + aLanguage = aValue; + break; + default: + OSL_FAIL("Wrong MemberId!"); + return false; + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/statcach.cxx b/sfx2/source/control/statcach.cxx new file mode 100644 index 000000000..ef1eaf1a1 --- /dev/null +++ b/sfx2/source/control/statcach.cxx @@ -0,0 +1,503 @@ +/* -*- 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 . + */ + + +#ifdef __sun +#include <ctime> +#endif + +#include <framework/dispatchhelper.hxx> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <comphelper/servicehelper.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <svl/visitem.hxx> + +#include <sfx2/app.hxx> +#include <statcach.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/ctrlitem.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxuno.hxx> +#include <unoctitm.hxx> +#include <sfx2/msgpool.hxx> +#include <sfx2/viewfrm.hxx> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; + +BindDispatch_Impl::BindDispatch_Impl( const css::uno::Reference< css::frame::XDispatch > & rDisp, const css::util::URL& rURL, SfxStateCache *pStateCache, const SfxSlot* pS ) + : xDisp( rDisp ) + , aURL( rURL ) + , pCache( pStateCache ) + , pSlot( pS ) +{ + DBG_ASSERT( pCache && pSlot, "Invalid BindDispatch!"); + aStatus.IsEnabled = true; +} + +void SAL_CALL BindDispatch_Impl::disposing( const css::lang::EventObject& ) +{ + if ( xDisp.is() ) + { + xDisp->removeStatusListener( static_cast<css::frame::XStatusListener*>(this), aURL ); + xDisp.clear(); + } +} + +void SAL_CALL BindDispatch_Impl::statusChanged( const css::frame::FeatureStateEvent& rEvent ) +{ + aStatus = rEvent; + if ( !pCache ) + return; + + css::uno::Reference< css::frame::XStatusListener > xKeepAlive(this); + if ( aStatus.Requery ) + pCache->Invalidate( true ); + else + { + std::unique_ptr<SfxPoolItem> pItem; + sal_uInt16 nId = pCache->GetId(); + SfxItemState eState = SfxItemState::DISABLED; + if ( !aStatus.IsEnabled ) + { + // default + } + else if (aStatus.State.hasValue()) + { + eState = SfxItemState::DEFAULT; + css::uno::Any aAny = aStatus.State; + + const css::uno::Type& aType = aAny.getValueType(); + if ( aType == cppu::UnoType< bool >::get() ) + { + bool bTemp = false; + aAny >>= bTemp ; + pItem.reset( new SfxBoolItem( nId, bTemp ) ); + } + else if ( aType == ::cppu::UnoType< ::cppu::UnoUnsignedShortType >::get() ) + { + sal_uInt16 nTemp = 0; + aAny >>= nTemp ; + pItem.reset( new SfxUInt16Item( nId, nTemp ) ); + } + else if ( aType == cppu::UnoType<sal_uInt32>::get() ) + { + sal_uInt32 nTemp = 0; + aAny >>= nTemp ; + pItem.reset( new SfxUInt32Item( nId, nTemp ) ); + } + else if ( aType == cppu::UnoType<OUString>::get() ) + { + OUString sTemp ; + aAny >>= sTemp ; + pItem.reset( new SfxStringItem( nId, sTemp ) ); + } + else + { + if ( pSlot ) + pItem = pSlot->GetType()->CreateItem(); + if ( pItem ) + { + pItem->SetWhich( nId ); + pItem->PutValue( aAny, 0 ); + } + else + pItem.reset( new SfxVoidItem( nId ) ); + } + } + else + { + // DONTCARE status + pItem.reset( new SfxVoidItem(0) ); + eState = SfxItemState::UNKNOWN; + } + + for ( SfxControllerItem *pCtrl = pCache->GetItemLink(); + pCtrl; + pCtrl = pCtrl->GetItemLink() ) + pCtrl->StateChangedAtToolBoxControl( nId, eState, pItem.get() ); + } +} + +void BindDispatch_Impl::Release() +{ + if ( xDisp.is() ) + { + try + { + xDisp->removeStatusListener(static_cast<css::frame::XStatusListener*>(this), aURL); + } + catch (const lang::DisposedException&) + { + TOOLS_WARN_EXCEPTION("sfx", "BindDispatch_Impl::Release: xDisp is disposed: "); + } + xDisp.clear(); + } + pCache = nullptr; +} + + +sal_Int16 BindDispatch_Impl::Dispatch( const css::uno::Sequence < css::beans::PropertyValue >& aProps, bool bForceSynchron ) +{ + sal_Int16 eRet = css::frame::DispatchResultState::DONTKNOW; + + if ( xDisp.is() && aStatus.IsEnabled ) + { + ::rtl::Reference< ::framework::DispatchHelper > xHelper( new ::framework::DispatchHelper(nullptr)); + css::uno::Any aResult = xHelper->executeDispatch(xDisp, aURL, bForceSynchron, aProps); + + css::frame::DispatchResultEvent aEvent; + aResult >>= aEvent; + + eRet = aEvent.State; + } + + return eRet; +} + + +// This constructor for an invalid cache that is updated in the first request. + +SfxStateCache::SfxStateCache( sal_uInt16 nFuncId ): + nId(nFuncId), + pInternalController(nullptr), + pController(nullptr), + pLastItem( nullptr ), + eLastState( SfxItemState::UNKNOWN ), + bItemVisible( true ) +{ + bCtrlDirty = true; + bSlotDirty = true; + bItemDirty = true; +} + + +// The Destructor checks by assertion, even if controllers are registered. + +SfxStateCache::~SfxStateCache() +{ + DBG_ASSERT( pController == nullptr && pInternalController == nullptr, "there are still Controllers registered" ); + if ( !IsInvalidItem(pLastItem) ) + delete pLastItem; + if ( mxDispatch.is() ) + mxDispatch->Release(); +} + + +// invalidates the cache (next request will force update) +void SfxStateCache::Invalidate( bool bWithMsg ) +{ + bCtrlDirty = true; + if ( bWithMsg ) + { + bSlotDirty = true; + aSlotServ.SetSlot( nullptr ); + if ( mxDispatch.is() ) + mxDispatch->Release(); + mxDispatch.clear(); + } +} + + +// gets the corresponding function from the dispatcher or the cache + +const SfxSlotServer* SfxStateCache::GetSlotServer( SfxDispatcher &rDispat , const css::uno::Reference< css::frame::XDispatchProvider > & xProv ) +{ + + if ( bSlotDirty ) + { + // get the SlotServer; we need it for internal controllers anyway, but also in most cases + rDispat.FindServer_( nId, aSlotServ ); + + DBG_ASSERT( !mxDispatch.is(), "Old Dispatch not removed!" ); + + // we don't need to check the dispatch provider if we only have an internal controller + if ( xProv.is() ) + { + const SfxSlot* pSlot = aSlotServ.GetSlot(); + if ( !pSlot ) + // get the slot - even if it is disabled on the dispatcher + pSlot = SfxSlotPool::GetSlotPool( rDispat.GetFrame() ).GetSlot( nId ); + + if ( !pSlot || !pSlot->pUnoName ) + { + bSlotDirty = false; + bCtrlDirty = true; + return aSlotServ.GetSlot()? &aSlotServ: nullptr; + } + + // create the dispatch URL from the slot data + 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; + + // try to get a dispatch object for this command + css::uno::Reference< css::frame::XDispatch > xDisp = xProv->queryDispatch( aURL, OUString(), 0 ); + if ( xDisp.is() ) + { + // test the dispatch object if it is just a wrapper for a SfxDispatcher + css::uno::Reference< css::lang::XUnoTunnel > xTunnel( xDisp, css::uno::UNO_QUERY ); + if (auto pDisp = comphelper::getFromUnoTunnel<SfxOfficeDispatch>(xTunnel)) + { + // The intercepting object is an SFX component + // If this dispatch object does not use the wanted dispatcher or the AppDispatcher, it's treated like any other UNO component + // (intercepting by internal dispatches) + SfxDispatcher *pDispatcher = pDisp->GetDispatcher_Impl(); + if ( pDispatcher == &rDispat || pDispatcher == SfxGetpApp()->GetAppDispatcher_Impl() ) + { + // so we can use it directly + bSlotDirty = false; + bCtrlDirty = true; + return aSlotServ.GetSlot()? &aSlotServ: nullptr; + } + } + + // so the dispatch object isn't a SfxDispatcher wrapper or it is one, but it uses another dispatcher, but not rDispat + mxDispatch = new BindDispatch_Impl( xDisp, aURL, this, pSlot ); + + // flags must be set before adding StatusListener because the dispatch object will set the state + bSlotDirty = false; + bCtrlDirty = true; + xDisp->addStatusListener( mxDispatch, aURL ); + } + else if ( rDispat.GetFrame() ) + { + css::uno::Reference < css::frame::XDispatchProvider > xFrameProv( + rDispat.GetFrame()->GetFrame().GetFrameInterface(), css::uno::UNO_QUERY ); + if ( xFrameProv != xProv ) + return GetSlotServer( rDispat, xFrameProv ); + } + } + + bSlotDirty = false; + bCtrlDirty = true; + } + + // we *always* return a SlotServer (if there is one); but in case of an external dispatch we might not use it + // for the "real" (non internal) controllers + return aSlotServ.GetSlot()? &aSlotServ: nullptr; +} + + +// Set Status in all Controllers + +void SfxStateCache::SetState +( + SfxItemState eState, // <SfxItemState> from 'pState' + const SfxPoolItem* pState, // Slot Status, 0 or -1 + bool bMaybeDirty +) + +/* [Description] + + This method distributes the status of all of this SID bound + <SfxControllerItem>s. If the value is the same as before, and if neither + controller was registered nor invalidated inbetween, then no value is + passed. This way the flickering is for example avoided in ListBoxes. +*/ +{ + SetState_Impl( eState, pState, bMaybeDirty ); +} + +void SfxStateCache::GetState +( + boost::property_tree::ptree& rState +) +{ + if ( !mxDispatch.is() && pController ) + { + for ( SfxControllerItem *pCtrl = pController; + pCtrl; + pCtrl = pCtrl->GetItemLink() ) + pCtrl->GetControlState( nId, rState ); + } +} + +void SfxStateCache::SetVisibleState( bool bShow ) +{ + if ( bShow == bItemVisible ) + return; + + SfxItemState eState( SfxItemState::DEFAULT ); + const SfxPoolItem* pState( nullptr ); + bool bDeleteItem( false ); + + bItemVisible = bShow; + if ( bShow ) + { + if ( IsInvalidItem(pLastItem) || ( pLastItem == nullptr )) + { + pState = new SfxVoidItem( nId ); + bDeleteItem = true; + } + else + pState = pLastItem; + + eState = eLastState; + } + else + { + pState = new SfxVisibilityItem( nId, false ); + bDeleteItem = true; + } + + // Update Controller + if ( !mxDispatch.is() && pController ) + { + for ( SfxControllerItem *pCtrl = pController; + pCtrl; + pCtrl = pCtrl->GetItemLink() ) + pCtrl->StateChangedAtToolBoxControl( nId, eState, pState ); + } + + if ( pInternalController ) + pInternalController->StateChangedAtToolBoxControl( nId, eState, pState ); + + if ( bDeleteItem ) + delete pState; +} + + +void SfxStateCache::SetState_Impl +( + SfxItemState eState, // <SfxItemState> from 'pState' + const SfxPoolItem* pState, // Slot Status, 0 or -1 + bool bMaybeDirty +) +{ + // If a hard update occurs between enter- and leave-registrations is a + // can also intermediate Cached exist without controller. + if ( !pController && !pInternalController ) + return; + + DBG_ASSERT( bMaybeDirty || !bSlotDirty, "setting state of dirty message" ); + DBG_ASSERT( SfxControllerItem::GetItemState(pState) == eState, "invalid SfxItemState" ); + + // does the controller have to be notified at all? + bool bNotify = bItemDirty; + if ( !bItemDirty ) + { + bool bBothAvailable = pLastItem && pState && + !IsInvalidItem(pState) && !IsInvalidItem(pLastItem); + DBG_ASSERT( !bBothAvailable || pState != pLastItem, "setting state with own item" ); + if ( bBothAvailable ) + bNotify = typeid(*pState) != typeid(*pLastItem) || + *pState != *pLastItem; + else + bNotify = ( pState != pLastItem ) || ( eState != eLastState ); + } + + if ( bNotify ) + { + // Update Controller + if ( !mxDispatch.is() && pController ) + { + for ( SfxControllerItem *pCtrl = pController; + pCtrl; + pCtrl = pCtrl->GetItemLink() ) + pCtrl->StateChangedAtToolBoxControl( nId, eState, pState ); + } + + if ( pInternalController ) + static_cast<SfxDispatchController_Impl *>(pInternalController)->StateChanged( nId, eState, pState, &aSlotServ ); + + // Remember new value + if ( !IsInvalidItem(pLastItem) ) + { + delete pLastItem; + pLastItem = nullptr; + } + if ( pState && !IsInvalidItem(pState) ) + pLastItem = pState->Clone(); + else + pLastItem = nullptr; + eLastState = eState; + bItemDirty = false; + } + + bCtrlDirty = false; +} + + +// Set old status again in all the controllers + +void SfxStateCache::SetCachedState( bool bAlways ) +{ + DBG_ASSERT(pController==nullptr||pController->GetId()==nId, "Cache with wrong ControllerItem" ); + + // Only update if cached item exists and also able to process. + // (If the State is sent, it must be ensured that a SlotServer is present, + // see SfxControllerItem:: GetCoreMetric()) + if ( !(bAlways || ( !bItemDirty && !bSlotDirty )) ) + return; + + // Update Controller + if ( !mxDispatch.is() && pController ) + { + for ( SfxControllerItem *pCtrl = pController; + pCtrl; + pCtrl = pCtrl->GetItemLink() ) + pCtrl->StateChangedAtToolBoxControl( nId, eLastState, pLastItem ); + } + + if ( pInternalController ) + static_cast<SfxDispatchController_Impl *>(pInternalController)->StateChanged( nId, eLastState, pLastItem, &aSlotServ ); + + // Controller is now ok + bCtrlDirty = true; +} + + +css::uno::Reference< css::frame::XDispatch > SfxStateCache::GetDispatch() const +{ + if ( mxDispatch.is() ) + return mxDispatch->xDisp; + return css::uno::Reference< css::frame::XDispatch > (); +} + +sal_Int16 SfxStateCache::Dispatch( const SfxItemSet* pSet, bool bForceSynchron ) +{ + // protect pDispatch against destruction in the call + rtl::Reference<BindDispatch_Impl> xKeepAlive( mxDispatch ); + sal_Int16 eRet = css::frame::DispatchResultState::DONTKNOW; + + if ( mxDispatch.is() ) + { + uno::Sequence < beans::PropertyValue > aArgs; + if (pSet) + TransformItems( nId, *pSet, aArgs ); + + eRet = mxDispatch->Dispatch( aArgs, bForceSynchron ); + } + + return eRet; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/templatecontaineritem.cxx b/sfx2/source/control/templatecontaineritem.cxx new file mode 100644 index 000000000..f8b07c6f6 --- /dev/null +++ b/sfx2/source/control/templatecontaineritem.cxx @@ -0,0 +1,20 @@ +/* -*- 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/. + */ + +#include <templatecontaineritem.hxx> + +TemplateContainerItem::TemplateContainerItem(sal_uInt16 nId) + : mnId(nId) + , mnRegionId(0) +{ +} + +TemplateContainerItem::~TemplateContainerItem() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/templatedefaultview.cxx b/sfx2/source/control/templatedefaultview.cxx new file mode 100644 index 000000000..da174413e --- /dev/null +++ b/sfx2/source/control/templatedefaultview.cxx @@ -0,0 +1,82 @@ +/* -*- 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/. + */ + +#include <templatedefaultview.hxx> +#include <sfx2/thumbnailview.hxx> +#include <templateviewitem.hxx> +#include <sfx2/sfxresid.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> + +#include <sfx2/strings.hrc> + +#include <officecfg/Office/Common.hxx> + +constexpr int gnItemPadding(5); //TODO:: Change padding to 10. It looks really crowded and occupied. +constexpr tools::Long gnTextHeight = 30; + +TemplateDefaultView::TemplateDefaultView(std::unique_ptr<weld::ScrolledWindow> xWindow, + std::unique_ptr<weld::Menu> xMenu) + : TemplateLocalView(std::move(xWindow), std::move(xMenu)) +{ + tools::Rectangle aScreen = Application::GetScreenPosSizePixel(Application::GetDisplayBuiltInScreen()); + tools::Long nItemMaxSize = std::min(aScreen.GetWidth(),aScreen.GetHeight()) > 800 ? 256 : 192; + ThumbnailView::setItemDimensions( nItemMaxSize, nItemMaxSize, gnTextHeight, gnItemPadding ); + updateThumbnailDimensions(nItemMaxSize); + + // startcenter specific settings + maFillColor = Color(ColorTransparency, officecfg::Office::Common::Help::StartCenter::StartCenterThumbnailsBackgroundColor::get()); + maTextColor = Color(ColorTransparency, officecfg::Office::Common::Help::StartCenter::StartCenterThumbnailsTextColor::get()); + maHighlightColor = Color(ColorTransparency, officecfg::Office::Common::Help::StartCenter::StartCenterThumbnailsHighlightColor::get()); + maHighlightTextColor = Color(ColorTransparency, officecfg::Office::Common::Help::StartCenter::StartCenterThumbnailsHighlightTextColor::get()); + mfHighlightTransparence = 0.25; + + UpdateColors(); +} + +void TemplateDefaultView::showAllTemplates() +{ + mnCurRegionId = 0; + + insertItems(maAllTemplates, false); +} + +bool TemplateDefaultView::KeyInput( const KeyEvent& rKEvt ) +{ + return ThumbnailView::KeyInput(rKEvt); +} + +bool TemplateDefaultView::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if( rMEvt.IsLeft() && rMEvt.GetClicks() == 1 ) + { + size_t nPos = ImplGetItem(rMEvt.GetPosPixel()); + ThumbnailViewItem* pItem = ImplGetItem(nPos); + TemplateViewItem* pViewItem = dynamic_cast<TemplateViewItem*>(pItem); + if(pViewItem) + maOpenTemplateHdl.Call(pViewItem); + return true; + } + + return TemplateLocalView::MouseButtonDown(rMEvt); +} + +void TemplateDefaultView::createContextMenu() +{ + mxContextMenu->clear(); + mxContextMenu->append("open",SfxResId(STR_OPEN)); + mxContextMenu->append("edit",SfxResId(STR_EDIT_TEMPLATE)); + deselectItems(); + maSelectedItem->setSelection(true); + maItemStateHdl.Call(maSelectedItem); + ContextMenuSelectHdl(mxContextMenu->popup_at_rect(GetDrawingArea(), tools::Rectangle(maPosition, Size(1,1)))); + Invalidate(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/templatedlglocalview.cxx b/sfx2/source/control/templatedlglocalview.cxx new file mode 100644 index 000000000..704468681 --- /dev/null +++ b/sfx2/source/control/templatedlglocalview.cxx @@ -0,0 +1,416 @@ +/* -*- 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/. + */ + +#include <sfx2/templatedlglocalview.hxx> + +#include <comphelper/string.hxx> +#include <sfx2/inputdlg.hxx> +#include <templateviewitem.hxx> +#include <sfx2/sfxresid.hxx> +#include <templatecontaineritem.hxx> +#include <sfx2/strings.hrc> +#include <vcl/commandevent.hxx> +#include <vcl/svapp.hxx> +#include <vcl/event.hxx> +#include <sfx2/doctempl.hxx> +#include <bitmaps.hlst> + +TemplateDlgLocalView::TemplateDlgLocalView(std::unique_ptr<weld::ScrolledWindow> xWindow, + std::unique_ptr<weld::Menu> xMenu, + std::unique_ptr<weld::TreeView> xTreeView) + : TemplateLocalView(std::move(xWindow), std::move(xMenu)) + , ListView(std::move(xTreeView)) + , mViewMode(TemplateViewMode::eThumbnailView) +{ + mxTreeView->connect_row_activated(LINK(this, TemplateDlgLocalView, RowActivatedHdl)); + mxTreeView->connect_column_clicked(LINK(this, ListView, ColumnClickedHdl)); + mxTreeView->connect_changed(LINK(this, TemplateDlgLocalView, ListViewChangedHdl)); + mxTreeView->connect_popup_menu(LINK(this, TemplateDlgLocalView, PopupMenuHdl)); + mxTreeView->connect_key_press(LINK(this, TemplateDlgLocalView, KeyPressHdl)); +} + +void TemplateDlgLocalView::showAllTemplates() +{ + mnCurRegionId = 0; + + insertItems(maAllTemplates, false, true); + + maOpenRegionHdl.Call(nullptr); +} + +void TemplateDlgLocalView::showRegion(TemplateContainerItem const* pItem) +{ + mnCurRegionId = pItem->mnRegionId + 1; + + insertItems(pItem->maTemplates, true, false); + + maOpenRegionHdl.Call(nullptr); +} + +void TemplateDlgLocalView::showRegion(std::u16string_view rName) +{ + for (auto const& pRegion : maRegions) + { + if (pRegion->maTitle == rName) + { + showRegion(pRegion.get()); + break; + } + } +} + +void TemplateDlgLocalView::reload() +{ + mpDocTemplates->Update(); + OUString sCurRegionName = getRegionItemName(mnCurRegionId); + Populate(); + mnCurRegionId = getRegionId(sCurRegionName); + + // Check if we are currently browsing a region or root folder + if (mnCurRegionId) + { + sal_uInt16 nRegionId = mnCurRegionId - 1; //Is offset by 1 + + for (auto const& pRegion : maRegions) + { + if (pRegion->mnRegionId == nRegionId) + { + showRegion(pRegion.get()); + break; + } + } + } + else + showAllTemplates(); + + //No items should be selected by default + ThumbnailView::deselectItems(); + ListView::unselect_all(); +} + +void TemplateDlgLocalView::createContextMenu(const bool bIsDefault, const bool bIsBuiltIn, + const bool bIsSingleSel, const OUString& rDefaultImg) +{ + mxContextMenu->clear(); + mxContextMenu->append("open", SfxResId(STR_OPEN), BMP_MENU_OPEN); + mxContextMenu->append("edit", SfxResId(STR_EDIT_TEMPLATE), BMP_MENU_EDIT); + + if (!bIsDefault) + mxContextMenu->append("default", SfxResId(STR_DEFAULT_TEMPLATE), rDefaultImg); + else + mxContextMenu->append("default", SfxResId(STR_RESET_DEFAULT), rDefaultImg); + + mxContextMenu->append_separator("separator1"); + mxContextMenu->append("rename", SfxResId(STR_SFX_RENAME), BMP_MENU_RENAME); + mxContextMenu->append("delete", SfxResId(STR_DELETE_TEMPLATE), BMP_MENU_DELETE); + mxContextMenu->append_separator("separator2"); + mxContextMenu->append("move", SfxResId(STR_MOVE), BMP_MENU_MOVE); + mxContextMenu->append("export", SfxResId(STR_EXPORT), BMP_MENU_EXPORT); + + if (!bIsSingleSel) + { + mxContextMenu->set_sensitive("open", false); + mxContextMenu->set_sensitive("edit", false); + mxContextMenu->set_sensitive("default", false); + mxContextMenu->set_sensitive("rename", false); + } + if (bIsBuiltIn) + { + mxContextMenu->set_sensitive("rename", false); + mxContextMenu->set_sensitive("delete", false); + } + if (mViewMode == TemplateViewMode::eThumbnailView) + { + ContextMenuSelectHdl(mxContextMenu->popup_at_rect( + GetDrawingArea(), tools::Rectangle(maPosition, Size(1, 1)))); + Invalidate(); + } + else if (mViewMode == TemplateViewMode::eListView) + ContextMenuSelectHdl(mxContextMenu->popup_at_rect( + mxTreeView.get(), tools::Rectangle(maPosition, Size(1, 1)))); +} + +void TemplateDlgLocalView::ContextMenuSelectHdl(std::string_view rIdent) +{ + if (rIdent == "open") + maOpenTemplateHdl.Call(maSelectedItem); + else if (rIdent == "edit") + maEditTemplateHdl.Call(maSelectedItem); + else if (rIdent == "rename") + { + InputDialog aTitleEditDlg(GetDrawingArea(), SfxResId(STR_RENAME_TEMPLATE)); + aTitleEditDlg.set_title(SfxResId(STR_WINDOW_TITLE_RENAME_TEMPLATE)); + OUString sOldTitle = maSelectedItem->getTitle(); + aTitleEditDlg.SetEntryText(sOldTitle); + aTitleEditDlg.HideHelpBtn(); + + auto aCurRegionItems = getFilteredItems([&](const TemplateItemProperties& rItem) { + return rItem.aRegionName == getRegionName(maSelectedItem->mnRegionId); + }); + OUString sTooltip(SfxResId(STR_TOOLTIP_ERROR_RENAME_TEMPLATE)); + sTooltip = sTooltip.replaceFirst("$2", getRegionName(maSelectedItem->mnRegionId)); + aTitleEditDlg.setCheckEntry([&](OUString sNewTitle) { + if (sNewTitle.isEmpty() || sNewTitle == sOldTitle) + return true; + for (const auto& rItem : aCurRegionItems) + { + if (rItem.aName == sNewTitle) + { + aTitleEditDlg.SetTooltip(sTooltip.replaceFirst("$1", sNewTitle)); + return false; + } + } + return true; + }); + if (!aTitleEditDlg.run()) + return; + OUString sNewTitle = comphelper::string::strip(aTitleEditDlg.GetEntryText(), ' '); + + if (!sNewTitle.isEmpty() && sNewTitle != sOldTitle) + { + maSelectedItem->setTitle(sNewTitle); + ListView::rename(OUString::number(maSelectedItem->mnId), maSelectedItem->maTitle); + } + } + else if (rIdent == "delete") + { + std::unique_ptr<weld::MessageDialog> xQueryDlg(Application::CreateMessageDialog( + GetDrawingArea(), VclMessageType::Question, VclButtonsType::YesNo, + SfxResId(STR_QMSG_SEL_TEMPLATE_DELETE))); + if (xQueryDlg->run() != RET_YES) + return; + + maDeleteTemplateHdl.Call(maSelectedItem); + reload(); + } + else if (rIdent == "default") + { + maDefaultTemplateHdl.Call(maSelectedItem); + ListView::refreshDefaultColumn(); + } + else if (rIdent == "move") + { + maMoveTemplateHdl.Call(maSelectedItem); + } + else if (rIdent == "export") + { + maExportTemplateHdl.Call(maSelectedItem); + } +} + +void TemplateDlgLocalView::insertFilteredItems() +{ + ListView::clearListView(); + for (const ThumbnailViewItem* rItem : mFilteredItemList) + { + const TemplateViewItem* pViewItem = static_cast<const TemplateViewItem*>(rItem); + if (!pViewItem) + return; + bool isDefault = pViewItem->IsDefaultTemplate(); + OUString sId = OUString::number(pViewItem->mnId); + ListView::AppendItem(sId, rItem->maTitle, getRegionName(pViewItem->mnRegionId), + pViewItem->getPath(), isDefault); + } + ListView::sort(); +} + +void TemplateDlgLocalView::insertItems(const std::vector<TemplateItemProperties>& rTemplates, + bool isRegionSelected = true, + bool bShowCategoryInTooltip = false) +{ + TemplateLocalView::insertItems(rTemplates, isRegionSelected, bShowCategoryInTooltip); + insertFilteredItems(); +} + +void TemplateDlgLocalView::setTemplateViewMode(TemplateViewMode eMode) { mViewMode = eMode; } + +void TemplateDlgLocalView::Show() +{ + if (mViewMode == TemplateViewMode::eListView) + { + ThumbnailView::Hide(); + ListView::ShowListView(); + } + else + { + ThumbnailView::Show(); + ListView::HideListView(); + } + syncCursor(); +} +void TemplateDlgLocalView::Hide() +{ + ThumbnailView::Hide(); + ListView::HideListView(); +} + +bool TemplateDlgLocalView::IsVisible() const +{ + return ThumbnailView::IsVisible() || ListView::IsListViewVisible(); +} + +void TemplateDlgLocalView::syncCursor() +{ + if (mViewMode == TemplateViewMode::eListView) + { + ListView::unselect_all(); + int nIndex = -1; + + for (auto it = mFilteredItemList.cbegin(); it != mFilteredItemList.cend(); ++it) + { + if ((*it)->mbSelected) + { + nIndex = -1; + nIndex = ListView::get_index((*it)->mnId); + if (nIndex >= 0) + { + ListView::set_cursor(nIndex); + ListView::select(nIndex); + break; + } + } + } + updateSelection(); + } + else + { + ThumbnailView::deselectItems(); + std::vector<int> aSelRows = ListView::get_selected_rows(); + if (aSelRows.empty()) + return; + sal_uInt16 nCursorId = ListView::get_cursor_nId(); + ThumbnailView::SelectItem(nCursorId); + MakeItemVisible(nCursorId); + + for (auto it = mFilteredItemList.begin(); it != mFilteredItemList.end(); ++it) + { + if ((*it)->mnId == nCursorId) + { + mpStartSelRange = it; + break; + } + } + + size_t nPos = GetItemPos(nCursorId); + ThumbnailViewItem* pItem = ImplGetItem(nPos); + const TemplateViewItem* pViewItem = dynamic_cast<const TemplateViewItem*>(pItem); + if (pViewItem) + maSelectedItem = dynamic_cast<TemplateViewItem*>(pItem); + } +} + +void TemplateDlgLocalView::updateSelection() +{ + ThumbnailView::deselectItems(); + for (auto nIndex : ListView::get_selected_rows()) + { + ThumbnailView::SelectItem(ListView::get_nId(nIndex)); + } + + sal_uInt16 nCursorId = ListView::get_cursor_nId(); + size_t nPos = GetItemPos(nCursorId); + ThumbnailViewItem* pItem = ImplGetItem(nPos); + const TemplateViewItem* pViewItem = dynamic_cast<const TemplateViewItem*>(pItem); + if (pViewItem) + maSelectedItem = dynamic_cast<TemplateViewItem*>(pItem); + return; +} + +IMPL_LINK_NOARG(TemplateDlgLocalView, RowActivatedHdl, weld::TreeView&, bool) +{ + maOpenTemplateHdl.Call(maSelectedItem); + return true; +} + +IMPL_LINK(TemplateDlgLocalView, PopupMenuHdl, const CommandEvent&, rCEvt, bool) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + + if (rCEvt.IsMouseEvent()) + { + if (ListView::get_selected_rows().empty()) + return true; + Point aPosition(rCEvt.GetMousePosPixel()); + maPosition = aPosition; + updateSelection(); + if (maSelectedItem) + maCreateContextMenuHdl.Call(maSelectedItem); + return true; + } + else + { + if (ListView::get_selected_rows().empty()) + return true; + maPosition = Point(0, 0); + updateSelection(); + if (maSelectedItem) + maCreateContextMenuHdl.Call(maSelectedItem); + return true; + } +} + +IMPL_LINK_NOARG(TemplateDlgLocalView, ListViewChangedHdl, weld::TreeView&, void) +{ + updateSelection(); +} + +bool TemplateDlgLocalView::KeyInput(const KeyEvent& rKEvt) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if (aKeyCode == (KEY_MOD1 | KEY_A)) + { + for (ThumbnailViewItem* pItem : mFilteredItemList) + { + if (!pItem->isSelected()) + { + pItem->setSelection(true); + maItemStateHdl.Call(pItem); + } + } + + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + return true; + } + else if (aKeyCode == KEY_DELETE && !mFilteredItemList.empty()) + { + std::unique_ptr<weld::MessageDialog> xQueryDlg(Application::CreateMessageDialog( + GetDrawingArea(), VclMessageType::Question, VclButtonsType::YesNo, + SfxResId(STR_QMSG_SEL_TEMPLATE_DELETE))); + if (xQueryDlg->run() != RET_YES) + return true; + + maDeleteTemplateHdl.Call(maSelectedItem); + reload(); + } + + return ThumbnailView::KeyInput(rKEvt); +} + +IMPL_LINK(TemplateDlgLocalView, KeyPressHdl, const KeyEvent&, rKEvt, bool) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if (aKeyCode == KEY_DELETE && !mFilteredItemList.empty() + && !ListView::get_selected_rows().empty()) + { + std::unique_ptr<weld::MessageDialog> xQueryDlg(Application::CreateMessageDialog( + mxTreeView.get(), VclMessageType::Question, VclButtonsType::YesNo, + SfxResId(STR_QMSG_SEL_TEMPLATE_DELETE))); + if (xQueryDlg->run() != RET_YES) + return true; + + maDeleteTemplateHdl.Call(maSelectedItem); + reload(); + } + return false; +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/templatelocalview.cxx b/sfx2/source/control/templatelocalview.cxx new file mode 100644 index 000000000..a223b62e1 --- /dev/null +++ b/sfx2/source/control/templatelocalview.cxx @@ -0,0 +1,945 @@ +/* -*- 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/. + */ + +#include <sfx2/templatelocalview.hxx> + +#include <comphelper/string.hxx> +#include <sfx2/doctempl.hxx> +#include <sfx2/inputdlg.hxx> +#include <sfx2/sfxresid.hxx> +#include <templatecontaineritem.hxx> +#include <templateviewitem.hxx> +#include <sfx2/docfac.hxx> +#include <tools/urlobj.hxx> +#include <unotools/moduleoptions.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> + +#include <sfx2/strings.hrc> +#include <bitmaps.hlst> + +#include <comphelper/processfactory.hxx> +#include <com/sun/star/util/thePathSettings.hpp> +#include <unotools/ucbhelper.hxx> +#include <sfxurlrelocator.hxx> +#include <../doc/doctemplateslocal.hxx> + +using namespace ::com::sun::star; + +bool ViewFilter_Application::isFilteredExtension(FILTER_APPLICATION filter, std::u16string_view rExt) +{ + bool bRet = rExt == u"ott" || rExt == u"stw" || rExt == u"oth" || rExt == u"dot" || rExt == u"dotx" || rExt == u"otm" + || rExt == u"ots" || rExt == u"stc" || rExt == u"xlt" || rExt == u"xltm" || rExt == u"xltx" + || rExt == u"otp" || rExt == u"sti" || rExt == u"pot" || rExt == u"potm" || rExt == u"potx" + || rExt == u"otg" || rExt == u"std"; + + if (filter == FILTER_APPLICATION::WRITER) + { + bRet = rExt == u"ott" || rExt == u"stw" || rExt == u"oth" || rExt == u"dot" || rExt == u"dotx" || rExt == u"otm"; + } + else if (filter == FILTER_APPLICATION::CALC) + { + bRet = rExt == u"ots" || rExt == u"stc" || rExt == u"xlt" || rExt == u"xltm" || rExt == u"xltx"; + } + else if (filter == FILTER_APPLICATION::IMPRESS) + { + bRet = rExt == u"otp" || rExt == u"sti" || rExt == u"pot" || rExt == u"potm" || rExt == u"potx"; + } + else if (filter == FILTER_APPLICATION::DRAW) + { + bRet = rExt == u"otg" || rExt == u"std"; + } + + return bRet; +} + +bool ViewFilter_Application::isValid (std::u16string_view rPath) const +{ + INetURLObject aUrl(rPath); + return isFilteredExtension(mApp, aUrl.getExtension()); +} + +bool ViewFilter_Application::operator () (const ThumbnailViewItem *pItem) +{ + const TemplateViewItem *pTempItem = dynamic_cast<const TemplateViewItem*>(pItem); + if (pTempItem) + return isValid(pTempItem->getPath()); + + return true; +} + +void TemplateLocalView::updateThumbnailDimensions(tools::Long itemMaxSize) +{ + mnThumbnailWidth = itemMaxSize; + mnThumbnailHeight = itemMaxSize; +} + +TemplateLocalView::TemplateLocalView(std::unique_ptr<weld::ScrolledWindow> xWindow, + std::unique_ptr<weld::Menu> xMenu) + : ThumbnailView(std::move(xWindow), std::move(xMenu)) + , mnCurRegionId(0) + , maSelectedItem(nullptr) + , mnThumbnailWidth(TEMPLATE_THUMBNAIL_MAX_WIDTH) + , mnThumbnailHeight(TEMPLATE_THUMBNAIL_MAX_HEIGHT) + , maPosition(0,0) + , mpDocTemplates(new SfxDocumentTemplates) +{ +} + +TemplateLocalView::~TemplateLocalView() +{ +} + +void TemplateLocalView::Populate() +{ + maRegions.clear(); + maAllTemplates.clear(); + + sal_uInt16 nCount = mpDocTemplates->GetRegionCount(); + for (sal_uInt16 i = 0; i < nCount; ++i) + { + OUString aRegionName(mpDocTemplates->GetFullRegionName(i)); + + std::unique_ptr<TemplateContainerItem> pItem(new TemplateContainerItem( i+1 )); + pItem->mnRegionId = i; + pItem->maTitle = aRegionName; + + sal_uInt16 nEntries = mpDocTemplates->GetCount(i); + + for (sal_uInt16 j = 0; j < nEntries; ++j) + { + OUString aName = mpDocTemplates->GetName(i,j); + OUString aURL = mpDocTemplates->GetPath(i,j); + + TemplateItemProperties aProperties; + aProperties.nId = j+1; + aProperties.nDocId = j; + aProperties.nRegionId = i; + aProperties.aName = aName; + aProperties.aPath = aURL; + aProperties.aRegionName = aRegionName; + aProperties.aThumbnail = TemplateLocalView::fetchThumbnail(aURL, + mnThumbnailWidth, + mnThumbnailHeight); + + pItem->maTemplates.push_back(aProperties); + maAllTemplates.push_back(aProperties); + } + + maRegions.push_back(std::move(pItem)); + } +} + +void TemplateLocalView::reload() +{ + mpDocTemplates->Update(); + OUString sCurRegionName = getRegionItemName(mnCurRegionId); + Populate(); + mnCurRegionId = getRegionId(sCurRegionName); + + // Check if we are currently browsing a region or root folder + if (mnCurRegionId) + { + sal_uInt16 nRegionId = mnCurRegionId - 1; //Is offset by 1 + + for (auto const & pRegion : maRegions) + { + if (pRegion->mnRegionId == nRegionId) + { + showRegion(pRegion.get()); + break; + } + } + } + else + showAllTemplates(); + + //No items should be selected by default + deselectItems(); +} + +void TemplateLocalView::showAllTemplates() +{ + mnCurRegionId = 0; + + insertItems(maAllTemplates, false, true); + + maOpenRegionHdl.Call(nullptr); +} + +void TemplateLocalView::showRegion(TemplateContainerItem const *pItem) +{ + mnCurRegionId = pItem->mnRegionId+1; + + insertItems(pItem->maTemplates); + + maOpenRegionHdl.Call(nullptr); +} + +TemplateContainerItem* TemplateLocalView::getRegion(std::u16string_view rName) +{ + for (auto const & pRegion : maRegions) + if (pRegion->maTitle == rName) + return pRegion.get(); + + return nullptr; +} + +void TemplateLocalView::ContextMenuSelectHdl(std::string_view rIdent) +{ + if (rIdent == "open") + maOpenTemplateHdl.Call(maSelectedItem); + else if (rIdent == "edit") + maEditTemplateHdl.Call(maSelectedItem); + else if (rIdent == "rename") + { + InputDialog aTitleEditDlg(GetDrawingArea(), SfxResId(STR_RENAME_TEMPLATE)); + OUString sOldTitle = maSelectedItem->getTitle(); + aTitleEditDlg.SetEntryText(sOldTitle); + aTitleEditDlg.HideHelpBtn(); + + auto aCurRegionItems = getFilteredItems([&](const TemplateItemProperties& rItem) { + return rItem.aRegionName == getRegionName(maSelectedItem->mnRegionId); + }); + OUString sTooltip(SfxResId(STR_TOOLTIP_ERROR_RENAME_TEMPLATE)); + sTooltip = sTooltip.replaceFirst("$2", getRegionName(maSelectedItem->mnRegionId)); + aTitleEditDlg.setCheckEntry([&](OUString sNewTitle) { + if (sNewTitle.isEmpty() || sNewTitle == sOldTitle) + return true; + for (const auto& rItem : aCurRegionItems) + { + if (rItem.aName == sNewTitle) + { + aTitleEditDlg.SetTooltip(sTooltip.replaceFirst("$1", sNewTitle)); + return false; + } + } + return true; + }); + if (!aTitleEditDlg.run()) + return; + OUString sNewTitle = comphelper::string::strip(aTitleEditDlg.GetEntryText(), ' '); + + if ( !sNewTitle.isEmpty() && sNewTitle != sOldTitle ) + { + maSelectedItem->setTitle(sNewTitle); + } + } + else if (rIdent == "delete") + { + std::unique_ptr<weld::MessageDialog> xQueryDlg(Application::CreateMessageDialog(GetDrawingArea(), VclMessageType::Question, VclButtonsType::YesNo, + SfxResId(STR_QMSG_SEL_TEMPLATE_DELETE))); + if (xQueryDlg->run() != RET_YES) + return; + + maDeleteTemplateHdl.Call(maSelectedItem); + reload(); + } + else if (rIdent == "default") + maDefaultTemplateHdl.Call(maSelectedItem); +} + +sal_uInt16 TemplateLocalView::getRegionId(size_t pos) const +{ + assert(pos < maRegions.size()); + + return maRegions[pos]->mnId; +} + +sal_uInt16 TemplateLocalView::getRegionId(std::u16string_view sRegion) const +{ + for (auto const & pRegion : maRegions) + { + if (pRegion->maTitle == sRegion) + return pRegion->mnId; + } + + return 0; +} + +OUString TemplateLocalView::getRegionName(const sal_uInt16 nRegionId) const +{ + return mpDocTemplates->GetRegionName(nRegionId); +} + +OUString TemplateLocalView::getRegionItemName(const sal_uInt16 nItemId) const +{ + for (auto const & pRegion : maRegions) + { + if (pRegion->mnId == nItemId) + return pRegion->maTitle; + } + + return OUString(); +} + +std::vector<OUString> TemplateLocalView::getFolderNames() +{ + size_t n = maRegions.size(); + std::vector<OUString> ret(n); + + for (size_t i = 0; i < n; ++i) + ret[i] = maRegions[i]->maTitle; + + return ret; +} + +std::vector<TemplateItemProperties> +TemplateLocalView::getFilteredItems(const std::function<bool (const TemplateItemProperties&)> &rFunc) const +{ + std::vector<TemplateItemProperties> aItems; + + if (mnCurRegionId) + { + TemplateContainerItem *pFolderItem = maRegions[mnCurRegionId-1].get(); + + for (const TemplateItemProperties & rItemProps : pFolderItem->maTemplates) + { + if (rFunc(rItemProps)) + aItems.push_back(rItemProps); + } + } + else + { + for (auto const & pFolderItem : maRegions) + { + for (const TemplateItemProperties & rItemProps : pFolderItem->maTemplates) + { + if (rFunc(rItemProps)) + aItems.push_back(rItemProps); + } + } + } + + return aItems; +} + +sal_uInt16 TemplateLocalView::createRegion(const OUString &rName) +{ + sal_uInt16 nRegionId = mpDocTemplates->GetRegionCount(); // Next regionId + sal_uInt16 nItemId = maRegions.size() + 1; + + if (!mpDocTemplates->InsertDir(rName,nRegionId)) + return 0; + + // Insert to the region cache list and to the thumbnail item list + std::unique_ptr<TemplateContainerItem> pItem(new TemplateContainerItem( nItemId )); + pItem->mnRegionId = nRegionId; + pItem->maTitle = rName; + + maRegions.push_back(std::move(pItem)); + + return nItemId; +} + +bool TemplateLocalView::renameRegion(std::u16string_view rTitle, const OUString &rNewTitle) +{ + TemplateContainerItem *pRegion = getRegion(rTitle); + + if(pRegion) + { + sal_uInt16 nRegionId = pRegion->mnRegionId; + return mpDocTemplates->SetName( rNewTitle, nRegionId, USHRT_MAX/*nDocId*/ ); + } + return false; +} + +bool TemplateLocalView::removeRegion(const sal_uInt16 nItemId) +{ + sal_uInt16 nRegionId = USHRT_MAX; + + // Remove from the region cache list + for (auto pRegionIt = maRegions.begin(); pRegionIt != maRegions.end();) + { + if ( (*pRegionIt)->mnId == nItemId ) + { + if (!mpDocTemplates->Delete((*pRegionIt)->mnRegionId,USHRT_MAX)) + return false; + + nRegionId = (*pRegionIt)->mnRegionId; + + pRegionIt = maRegions.erase(pRegionIt); + } + else + { + // Synchronize regions cache ids with SfxDocumentTemplates + if (nRegionId != USHRT_MAX && (*pRegionIt)->mnRegionId > nRegionId) + --(*pRegionIt)->mnRegionId; + + ++pRegionIt; + } + } + + if (nRegionId == USHRT_MAX) + return false; + + // Synchronize view regions ids with SfxDocumentTemplates + for (auto const& region : maRegions) + { + if (region->mnRegionId > nRegionId) + --region->mnRegionId; + } + + return true; +} + +bool TemplateLocalView::removeTemplate (const sal_uInt16 nItemId, const sal_uInt16 nSrcItemId) +{ + for (auto const & pRegion : maRegions) + { + if (pRegion->mnId == nSrcItemId) + { + TemplateContainerItem *pItem = pRegion.get(); + auto pIter = std::find_if(pItem->maTemplates.begin(), pItem->maTemplates.end(), + [nItemId](const TemplateItemProperties& rTemplate) { return rTemplate.nId == nItemId; }); + if (pIter != pItem->maTemplates.end()) + { + if (!mpDocTemplates->Delete(pItem->mnRegionId,pIter->nDocId)) + return false; + + pIter = pItem->maTemplates.erase(pIter); + + if (pRegion->mnRegionId == mnCurRegionId-1) + { + RemoveItem(nItemId); + Invalidate(); + } + + // Update Doc Idx for all templates that follow + for (; pIter != pItem->maTemplates.end(); ++pIter) + pIter->nDocId = pIter->nDocId - 1; + } + + CalculateItemPositions(); + break; + } + } + + return true; +} + +void TemplateLocalView::moveTemplates(const std::set<const ThumbnailViewItem*, selection_cmp_fn> &rItems, + const sal_uInt16 nTargetItem) +{ + TemplateContainerItem *pTarget = nullptr; + TemplateContainerItem *pSrc = nullptr; + + for (auto const & pRegion : maRegions) + { + if (pRegion->mnId == nTargetItem) + pTarget = pRegion.get(); + } + + if (!pTarget) + return; + + bool refresh = false; + + sal_uInt16 nTargetRegion = pTarget->mnRegionId; + sal_uInt16 nTargetIdx = mpDocTemplates->GetCount(nTargetRegion); // Next Idx + std::vector<sal_uInt16> aItemIds; // List of moved items ids (also prevents the invalidation of rItems iterators when we remove them as we go) + + std::set<const ThumbnailViewItem*,selection_cmp_fn>::const_iterator aSelIter; + for ( aSelIter = rItems.begin(); aSelIter != rItems.end(); ++aSelIter, ++nTargetIdx ) + { + const TemplateViewItem *pViewItem = static_cast<const TemplateViewItem*>(*aSelIter); + sal_uInt16 nSrcRegionId = pViewItem->mnRegionId; + + for (auto const & pRegion : maRegions) + { + if (pRegion->mnRegionId == nSrcRegionId) + pSrc = pRegion.get(); + } + + if(pSrc) + { + bool bCopy = !mpDocTemplates->Move(nTargetRegion,nTargetIdx,nSrcRegionId,pViewItem->mnDocId); + + if (bCopy) + { + OUString sQuery = SfxResId(STR_MSG_QUERY_COPY).replaceFirst("$1", pViewItem->maTitle).replaceFirst("$2", + getRegionName(nTargetRegion)); + std::unique_ptr<weld::MessageDialog> xQueryDlg(Application::CreateMessageDialog(GetDrawingArea(), VclMessageType::Question, VclButtonsType::YesNo, sQuery)); + if (xQueryDlg->run() != RET_YES) + { + OUString sMsg(SfxResId(STR_MSG_ERROR_LOCAL_MOVE)); + sMsg = sMsg.replaceFirst("$1",getRegionName(nTargetRegion)); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetDrawingArea(), + VclMessageType::Warning, VclButtonsType::Ok, sMsg.replaceFirst( "$2",pViewItem->maTitle))); + xBox->run(); + + return; //return if any single move operation fails + } + + if (!mpDocTemplates->Copy(nTargetRegion,nTargetIdx,nSrcRegionId,pViewItem->mnDocId)) + { + continue; + } + } + + // move template to destination + + TemplateItemProperties aTemplateItem; + aTemplateItem.nId = nTargetIdx + 1; + aTemplateItem.nDocId = nTargetIdx; + aTemplateItem.nRegionId = nTargetRegion; + aTemplateItem.aName = pViewItem->maTitle; + aTemplateItem.aPath = mpDocTemplates->GetPath(nTargetRegion,nTargetIdx); + aTemplateItem.aRegionName = pViewItem->maHelpText; + aTemplateItem.aThumbnail = pViewItem->maPreview1; + + pTarget->maTemplates.push_back(aTemplateItem); + + if (!bCopy) + { + // remove template from region cached data + + std::vector<TemplateItemProperties>::iterator pPropIter; + for (pPropIter = pSrc->maTemplates.begin(); pPropIter != pSrc->maTemplates.end();) + { + if (pPropIter->nDocId == pViewItem->mnDocId) + { + pPropIter = pSrc->maTemplates.erase(pPropIter); + aItemIds.push_back(pViewItem->mnDocId + 1);//mnid + } + else + { + // Keep region document id synchronized with SfxDocumentTemplates + if (pPropIter->nDocId > pViewItem->mnDocId) + --pPropIter->nDocId; + + ++pPropIter; + } + } + + // Keep view document id synchronized with SfxDocumentTemplates + for (auto const& item : mItemList) + { + auto pTemplateViewItem = static_cast<TemplateViewItem*>(item.get()); + if (pTemplateViewItem->mnDocId > pViewItem->mnDocId) + --pTemplateViewItem->mnDocId; + } + } + } + + refresh = true; + } + + // Remove items from the current view + for (auto const& itemId : aItemIds) + RemoveItem(itemId); + + if (refresh) + { + CalculateItemPositions(); + Invalidate(); + } +} + +bool TemplateLocalView::copyFrom (TemplateContainerItem *pItem, const OUString &rPath) +{ + sal_uInt16 nId = 1; + sal_uInt16 nDocId = 0; + sal_uInt16 nRegionId = pItem->mnRegionId; + OUString aPath(rPath); + + if (!pItem->maTemplates.empty()) + { + nId = pItem->maTemplates.back().nId+1; + nDocId = pItem->maTemplates.back().nDocId+1; + } + + if (mpDocTemplates->CopyFrom(nRegionId,nDocId,aPath)) + { + TemplateItemProperties aTemplate; + aTemplate.nId = nId; + aTemplate.nDocId = nDocId; + aTemplate.nRegionId = nRegionId; + aTemplate.aName = aPath; + aTemplate.aThumbnail = TemplateLocalView::fetchThumbnail(rPath, + TEMPLATE_THUMBNAIL_MAX_WIDTH, + TEMPLATE_THUMBNAIL_MAX_HEIGHT); + aTemplate.aPath = rPath; + aTemplate.aRegionName = getRegionName(nRegionId); + + pItem->maTemplates.push_back(aTemplate); + + CalculateItemPositions(); + + return true; + } + + return false; +} + +bool TemplateLocalView::exportTo(const sal_uInt16 nItemId, const sal_uInt16 nRegionItemId, std::u16string_view rName) +{ + for (auto const & pRegItem : maRegions) + { + if (pRegItem->mnId == nRegionItemId) + { + for (auto const& elem : pRegItem->maTemplates) + { + if (elem.nId == nItemId) + { + return mpDocTemplates->CopyTo(pRegItem->mnRegionId,elem.nDocId,rName); + } + } + + break; + } + } + + return false; +} + +bool TemplateLocalView::renameItem(ThumbnailViewItem* pItem, const OUString& sNewTitle) +{ + sal_uInt16 nRegionId = 0; + sal_uInt16 nDocId = USHRT_MAX; + TemplateViewItem* pDocItem = dynamic_cast<TemplateViewItem*>( pItem ); + + if ( pDocItem ) + { + nRegionId = pDocItem->mnRegionId; + nDocId = pDocItem->mnDocId; + } + + bool bRes = mpDocTemplates->SetName( sNewTitle, nRegionId, nDocId ); + if(bRes) + { + for (auto & pRegion : maRegions) + { + if (pRegion->mnId == nRegionId + 1 ) + { + for(auto & aTemplate : pRegion->maTemplates) + { + if(aTemplate.nId == nDocId + 1) + { + aTemplate.aName = sNewTitle; + break; + } + } + break; + } + } + OUString sRegionName; + for (auto & aTemplate : maAllTemplates) + { + if (aTemplate.nRegionId == nRegionId && aTemplate.nDocId == nDocId) + { + aTemplate.aName = sNewTitle; + sRegionName = aTemplate.aRegionName; + break; + } + } + + OUString sHelpText = SfxResId(STR_TEMPLATE_TOOLTIP); + sHelpText = (sHelpText.replaceFirst("$1", sNewTitle)).replaceFirst("$2", sRegionName); + pItem->setHelpText(sHelpText); + pItem->maTitle = sNewTitle; + } + return bRes; +} + +void TemplateLocalView::insertItems(const std::vector<TemplateItemProperties> &rTemplates, bool isRegionSelected, bool bShowCategoryInTooltip) +{ + std::vector<std::unique_ptr<ThumbnailViewItem>> aItems(rTemplates.size()); + for (size_t i = 0, n = rTemplates.size(); i < n; ++i ) + { + const TemplateItemProperties *pCur = &rTemplates[i]; + + std::unique_ptr<TemplateViewItem> pChild; + if(isRegionSelected) + pChild.reset(new TemplateViewItem(*this, pCur->nId)); + else + pChild.reset(new TemplateViewItem(*this, i+1)); + + pChild->mnDocId = pCur->nDocId; + pChild->mnRegionId = pCur->nRegionId; + pChild->maTitle = pCur->aName; + pChild->setPath(pCur->aPath); + + if(!bShowCategoryInTooltip) + pChild->setHelpText(pCur->aName); + else + { + OUString sHelpText = SfxResId(STR_TEMPLATE_TOOLTIP); + sHelpText = (sHelpText.replaceFirst("$1", pCur->aName)).replaceFirst("$2", pCur->aRegionName); + pChild->setHelpText(sHelpText); + } + + pChild->maPreview1 = pCur->aThumbnail; + + if(IsDefaultTemplate(pCur->aPath)) + pChild->showDefaultIcon(true); + + if ( pCur->aThumbnail.IsEmpty() ) + { + // Use the default thumbnail if we have nothing else + pChild->maPreview1 = TemplateLocalView::getDefaultThumbnail(pCur->aPath); + } + + aItems[i] = std::move(pChild); + } + + updateItems(std::move(aItems)); +} + +bool TemplateLocalView::MouseButtonDown( const MouseEvent& rMEvt ) +{ + GrabFocus(); + return ThumbnailView::MouseButtonDown(rMEvt); +} + +bool TemplateLocalView::Command(const CommandEvent& rCEvt) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return CustomWidgetController::Command(rCEvt); + + if (rCEvt.IsMouseEvent()) + { + size_t nPos = ImplGetItem(rCEvt.GetMousePosPixel()); + Point aPosition(rCEvt.GetMousePosPixel()); + maPosition = aPosition; + ThumbnailViewItem* pItem = ImplGetItem(nPos); + const TemplateViewItem *pViewItem = dynamic_cast<const TemplateViewItem*>(pItem); + + if(pViewItem) + { + if(!pItem->isSelected()) + { + deselectItems(); + pItem->setSelection(true); + maItemStateHdl.Call(pItem); + } + maSelectedItem = dynamic_cast<TemplateViewItem*>(pItem); + maCreateContextMenuHdl.Call(pItem); + } + } + else + { + for (ThumbnailViewItem* pItem : mFilteredItemList) + { + if (pItem->isSelected()) + { + tools::Rectangle aRect = pItem->getDrawArea(); + maPosition = aRect.Center(); + maSelectedItem = dynamic_cast<TemplateViewItem*>(pItem); + maCreateContextMenuHdl.Call(pItem); + break; + } + } + } + return true; +} + +bool TemplateLocalView::KeyInput( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if(aKeyCode == ( KEY_MOD1 | KEY_A ) ) + { + for (ThumbnailViewItem* pItem : mFilteredItemList) + { + if (!pItem->isSelected()) + { + pItem->setSelection(true); + maItemStateHdl.Call(pItem); + } + } + + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + return true; + } + else if( aKeyCode == KEY_DELETE && !mFilteredItemList.empty()) + { + std::unique_ptr<weld::MessageDialog> xQueryDlg(Application::CreateMessageDialog(GetDrawingArea(), VclMessageType::Question, VclButtonsType::YesNo, + SfxResId(STR_QMSG_SEL_TEMPLATE_DELETE))); + if (xQueryDlg->run() != RET_YES) + return true; + + //copy to avoid changing filtered item list during deletion + ThumbnailValueItemList mFilteredItemListCopy = mFilteredItemList; + + for (ThumbnailViewItem* pItem : mFilteredItemListCopy) + { + if (pItem->isSelected()) + { + maDeleteTemplateHdl.Call(pItem); + } + } + reload(); + } + + return ThumbnailView::KeyInput(rKEvt); +} + +void TemplateLocalView::setOpenRegionHdl(const Link<void*,void> &rLink) +{ + maOpenRegionHdl = rLink; +} + +void TemplateLocalView::setCreateContextMenuHdl(const Link<ThumbnailViewItem*,void> &rLink) +{ + maCreateContextMenuHdl = rLink; +} + +void TemplateLocalView::setOpenTemplateHdl(const Link<ThumbnailViewItem*,void> &rLink) +{ + maOpenTemplateHdl = rLink; +} + +void TemplateLocalView::setEditTemplateHdl(const Link<ThumbnailViewItem*,void> &rLink) +{ + maEditTemplateHdl = rLink; +} + +void TemplateLocalView::setDeleteTemplateHdl(const Link<void*,void> &rLink) +{ + maDeleteTemplateHdl = rLink; +} + +void TemplateLocalView::setDefaultTemplateHdl(const Link<ThumbnailViewItem*,void> &rLink) +{ + maDefaultTemplateHdl = rLink; +} + +void TemplateLocalView::setMoveTemplateHdl(const Link<void*,void> &rLink) +{ + maMoveTemplateHdl = rLink; +} + +void TemplateLocalView::setExportTemplateHdl(const Link<void*,void> &rLink) +{ + maExportTemplateHdl = rLink; +} + +BitmapEx TemplateLocalView::scaleImg (const BitmapEx &rImg, tools::Long width, tools::Long height) +{ + BitmapEx aImg = rImg; + + if (!rImg.IsEmpty()) + { + Size aSize = rImg.GetSizePixel(); + + if (aSize.Width() == 0) + aSize.setWidth( 1 ); + + if (aSize.Height() == 0) + aSize.setHeight( 1 ); + + // make the picture fit the given width/height constraints + double nRatio = std::min(double(width)/double(aSize.Width()), double(height)/double(aSize.Height())); + + aImg.Scale(nRatio, nRatio); + } + + return aImg; +} + +bool TemplateLocalView::IsDefaultTemplate(const OUString& rPath) +{ + SvtModuleOptions aModOpt; + const css::uno::Sequence<OUString> &aServiceNames = aModOpt.GetAllServiceNames(); + + return std::any_of(aServiceNames.begin(), aServiceNames.end(), [&rPath](const OUString& rName) { + return SfxObjectFactory::GetStandardTemplate(rName).match(rPath); }); +} + +void TemplateLocalView::RemoveDefaultTemplateIcon(std::u16string_view rPath) +{ + for (const std::unique_ptr<ThumbnailViewItem>& pItem : mItemList) + { + TemplateViewItem* pViewItem = dynamic_cast<TemplateViewItem*>(pItem.get()); + if (pViewItem && pViewItem->getPath().match(rPath)) + { + pViewItem->showDefaultIcon(false); + Invalidate(); + return; + } + } +} + +BitmapEx TemplateLocalView::getDefaultThumbnail( std::u16string_view rPath ) +{ + BitmapEx aImg; + INetURLObject aUrl(rPath); + OUString aExt = aUrl.getExtension(); + + if ( ViewFilter_Application::isFilteredExtension( FILTER_APPLICATION::WRITER, aExt) ) + aImg = BitmapEx(SFX_THUMBNAIL_TEXT); + else if ( ViewFilter_Application::isFilteredExtension( FILTER_APPLICATION::CALC, aExt) ) + aImg = BitmapEx(SFX_THUMBNAIL_SHEET); + else if ( ViewFilter_Application::isFilteredExtension( FILTER_APPLICATION::IMPRESS, aExt) ) + aImg = BitmapEx(SFX_THUMBNAIL_PRESENTATION); + else if ( ViewFilter_Application::isFilteredExtension( FILTER_APPLICATION::DRAW, aExt) ) + aImg = BitmapEx(SFX_THUMBNAIL_DRAWING); + + return aImg; +} + +BitmapEx TemplateLocalView::fetchThumbnail (const OUString &msURL, tools::Long width, tools::Long height) +{ + return TemplateLocalView::scaleImg(ThumbnailView::readThumbnail(msURL), width, height); +} + +void TemplateLocalView::OnItemDblClicked (ThumbnailViewItem *pItem) +{ + TemplateViewItem* pViewItem = dynamic_cast<TemplateViewItem*>(pItem); + + if( pViewItem ) + maOpenTemplateHdl.Call(pViewItem); +} + +bool TemplateLocalView::IsInternalTemplate(const OUString& rPath) +{ + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + css::uno::Reference< css::util::XPathSettings > xPathSettings = css::util::thePathSettings::get(xContext); + uno::Sequence<OUString> aInternalTemplateDirs; + uno::Any aAny = xPathSettings->getPropertyValue("Template_internal"); + aAny >>= aInternalTemplateDirs; + SfxURLRelocator_Impl aRelocator(xContext); + for (OUString& rInternalTemplateDir : asNonConstRange(aInternalTemplateDirs)) + { + aRelocator.makeRelocatableURL(rInternalTemplateDir); + aRelocator.makeAbsoluteURL(rInternalTemplateDir); + if(::utl::UCBContentHelper::IsSubPath(rInternalTemplateDir, rPath)) + return true; + } + return false; +} + +bool TemplateLocalView::IsBuiltInRegion(const OUString& rRegionName) +{ + bool isBuiltInCategory = false; + auto aGroupNames = DocTemplLocaleHelper::GetBuiltInGroupNames(); + isBuiltInCategory = std::find(aGroupNames.begin(), aGroupNames.end(), + rRegionName) != aGroupNames.end(); + if(isBuiltInCategory) + return true; + //check if it contains any internal template + for(const auto& rItem : maRegions) + { + if(rItem->maTitle == rRegionName) + { + for(const auto& rTemplateItem : rItem->maTemplates) + { + if(IsInternalTemplate(rTemplateItem.aPath)) + return true; + } + break; + } + } + return false; +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/templateviewitem.cxx b/sfx2/source/control/templateviewitem.cxx new file mode 100644 index 000000000..28ff1f431 --- /dev/null +++ b/sfx2/source/control/templateviewitem.cxx @@ -0,0 +1,124 @@ +/* -*- 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/. + */ + +#include <templateviewitem.hxx> + +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/attribute/fillgraphicattribute.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonSelectionPrimitive2D.hxx> +#include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <tools/poly.hxx> +#include <vcl/graph.hxx> + +#include <bitmaps.hlst> + +using namespace basegfx; +using namespace basegfx::utils; +using namespace drawinglayer::attribute; +using namespace drawinglayer::primitive2d; + +TemplateViewItem::TemplateViewItem(ThumbnailView& rView, sal_uInt16 nId) + : ThumbnailViewItem(rView, nId), + mnRegionId(USHRT_MAX), + mnDocId(USHRT_MAX), + maDefaultBitmap(BMP_DEFAULT), + mbIsDefaultTemplate(false) +{ +} + +TemplateViewItem::~TemplateViewItem () +{ +} + +::tools::Rectangle TemplateViewItem::getDefaultIconArea() const +{ + ::tools::Rectangle aArea(getDrawArea()); + Size aSize(maDefaultBitmap.GetSizePixel()); + + return ::tools::Rectangle( + Point(aArea.Left() + THUMBNAILVIEW_ITEM_CORNER, aArea.Top() + THUMBNAILVIEW_ITEM_CORNER), + aSize); +} + +void TemplateViewItem::Paint(drawinglayer::processor2d::BaseProcessor2D *pProcessor, + const ThumbnailItemAttributes *pAttrs) +{ + BColor aFillColor = pAttrs->aFillColor; + + drawinglayer::primitive2d::Primitive2DContainer aSeq(5); + double fTransparence = 0.0; + + // Draw background + if( mbSelected && mbHover) + aFillColor = pAttrs->aSelectHighlightColor; + else if (mbSelected || mbHover) + { + aFillColor = pAttrs->aHighlightColor; + if (mbHover) + fTransparence = pAttrs->fHighlightTransparence; + } + + aSeq[0] = drawinglayer::primitive2d::Primitive2DReference( + new PolyPolygonSelectionPrimitive2D( B2DPolyPolygon(::tools::Polygon(maDrawArea,5,5).getB2DPolygon()), + aFillColor, + fTransparence, + 0.0, + true)); + + // Draw thumbnail + Size aImageSize = maPreview1.GetSizePixel(); + + float fWidth = aImageSize.Width(); + float fHeight = aImageSize.Height(); + float fPosX = maPrev1Pos.getX(); + float fPosY = maPrev1Pos.getY(); + + B2DPolygon aBounds; + aBounds.append(B2DPoint(fPosX,fPosY)); + aBounds.append(B2DPoint(fPosX+fWidth,fPosY)); + aBounds.append(B2DPoint(fPosX+fWidth,fPosY+fHeight)); + aBounds.append(B2DPoint(fPosX,fPosY+fHeight)); + aBounds.setClosed(true); + + aSeq[1] = drawinglayer::primitive2d::Primitive2DReference( new PolyPolygonColorPrimitive2D( + B2DPolyPolygon(aBounds), COL_WHITE.getBColor())); + + aSeq[2] = drawinglayer::primitive2d::Primitive2DReference( new FillGraphicPrimitive2D( + createTranslateB2DHomMatrix(maPrev1Pos.X(),maPrev1Pos.Y()), + FillGraphicAttribute(Graphic(maPreview1), + B2DRange( + B2DPoint(0,0), + B2DPoint(aImageSize.Width(),aImageSize.Height())), + false) + )); + + // draw thumbnail borders + aSeq[3] = drawinglayer::primitive2d::Primitive2DReference(createBorderLine(aBounds)); + + if(mbIsDefaultTemplate) + { + Point aIconPos(getDefaultIconArea().TopLeft()); + + aSeq[4] = drawinglayer::primitive2d::Primitive2DReference(new DiscreteBitmapPrimitive2D( maDefaultBitmap, + B2DPoint(aIconPos.X(), aIconPos.Y()))); + } + + addTextPrimitives(maTitle, pAttrs, maTextPos, aSeq); + + pProcessor->process(aSeq); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ + + diff --git a/sfx2/source/control/thumbnailview.cxx b/sfx2/source/control/thumbnailview.cxx new file mode 100644 index 000000000..a678df5b1 --- /dev/null +++ b/sfx2/source/control/thumbnailview.cxx @@ -0,0 +1,1220 @@ +/* -*- 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/. + */ + +#include <sfx2/thumbnailview.hxx> +#include <sfx2/thumbnailviewitem.hxx> + +#include <utility> + +#include "thumbnailviewacc.hxx" + +#include <basegfx/color/bcolortools.hxx> +#include <comphelper/processfactory.hxx> +#include <drawinglayer/attribute/fontattribute.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/Primitive2DContainer.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <o3tl/safeint.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/ucbstreamhelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/event.hxx> +#include <vcl/filter/PngImageReader.hxx> +#include <vcl/weldutils.hxx> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/StorageFactory.hpp> +#include <com/sun/star/embed/XStorage.hpp> + +#include <memory> + +using namespace basegfx; +using namespace basegfx::utils; +using namespace drawinglayer::attribute; +using namespace drawinglayer::primitive2d; + +constexpr int gnFineness = 5; + +bool ThumbnailView::renameItem(ThumbnailViewItem*, const OUString&) +{ + // Do nothing by default + return false; +} + +BitmapEx ThumbnailView::readThumbnail(const OUString &msURL) +{ + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + + // Load the thumbnail from a template document. + uno::Reference<io::XInputStream> xIStream; + + uno::Reference< uno::XComponentContext > xContext(::comphelper::getProcessComponentContext()); + try + { + uno::Reference<lang::XSingleServiceFactory> xStorageFactory = embed::StorageFactory::create(xContext); + + uno::Sequence<uno::Any> aArgs{ uno::Any(msURL), uno::Any(embed::ElementModes::READ) }; + uno::Reference<embed::XStorage> xDocStorage ( + xStorageFactory->createInstanceWithArguments(aArgs), + uno::UNO_QUERY); + + try + { + if (xDocStorage.is()) + { + uno::Reference<embed::XStorage> xStorage ( + xDocStorage->openStorageElement( + "Thumbnails", + embed::ElementModes::READ)); + if (xStorage.is()) + { + uno::Reference<io::XStream> xThumbnailCopy ( + xStorage->cloneStreamElement("thumbnail.png")); + if (xThumbnailCopy.is()) + xIStream = xThumbnailCopy->getInputStream(); + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sfx", + "caught exception while trying to access Thumbnail/thumbnail.png of " << msURL); + } + + try + { + // An (older) implementation had a bug - The storage + // name was "Thumbnail" instead of "Thumbnails". The + // old name is still used as fallback but this code can + // be removed soon. + if ( ! xIStream.is()) + { + uno::Reference<embed::XStorage> xStorage ( + xDocStorage->openStorageElement( "Thumbnail", + embed::ElementModes::READ)); + if (xStorage.is()) + { + uno::Reference<io::XStream> xThumbnailCopy ( + xStorage->cloneStreamElement("thumbnail.png")); + if (xThumbnailCopy.is()) + xIStream = xThumbnailCopy->getInputStream(); + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sfx", + "caught exception while trying to access Thumbnails/thumbnail.png of " << msURL); + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sfx", + "caught exception while trying to access thumbnail of " + << msURL); + } + + // Extract the image from the stream. + BitmapEx aThumbnail; + if (xIStream.is()) + { + std::unique_ptr<SvStream> pStream ( + ::utl::UcbStreamHelper::CreateStream (xIStream)); + vcl::PngImageReader aReader (*pStream); + aThumbnail = aReader.read (); + } + + // Note that the preview is returned without scaling it to the desired + // width. This gives the caller the chance to take advantage of a + // possibly larger resolution then was asked for. + return aThumbnail; +} + +ThumbnailView::ThumbnailView(std::unique_ptr<weld::ScrolledWindow> xWindow, std::unique_ptr<weld::Menu> xMenu) + : mnThumbnailHeight(0) + , mnDisplayHeight(0) + , mnVItemSpace(-1) + , mbAllowVScrollBar(xWindow->get_vpolicy() != VclPolicyType::NEVER) + , mbSelectOnFocus(true) + , mpItemAttrs(new ThumbnailItemAttributes) + , mxScrolledWindow(std::move(xWindow)) + , mxContextMenu(std::move(xMenu)) +{ + ImplInit(); + mxScrolledWindow->connect_vadjustment_changed(LINK(this, ThumbnailView, ImplScrollHdl)); +} + +ThumbnailView::~ThumbnailView() +{ + css::uno::Reference< css::lang::XComponent> xComponent(mxAccessible, css::uno::UNO_QUERY); + + if (xComponent.is()) + xComponent->dispose(); + + mpItemAttrs.reset(); + + ImplDeleteItems(); +} + +bool ThumbnailView::MouseMove(const MouseEvent& rMEvt) +{ + size_t nItemCount = mFilteredItemList.size(); + Point aPoint = rMEvt.GetPosPixel(); + + for (size_t i = 0; i < nItemCount; i++) + { + ThumbnailViewItem *pItem = mFilteredItemList[i]; + ::tools::Rectangle aToInvalidate(pItem->updateHighlight(pItem->mbVisible && !rMEvt.IsLeaveWindow(), aPoint)); + if (!aToInvalidate.IsEmpty() && IsReallyVisible() && IsUpdateMode()) + Invalidate(aToInvalidate); + } + + return true; +} + +OUString ThumbnailView::RequestHelp(tools::Rectangle& rHelpRect) +{ + if (!mbShowTooltips) + return OUString(); + + Point aPos = rHelpRect.TopLeft(); + size_t nItemCount = mFilteredItemList.size(); + for (size_t i = 0; i < nItemCount; i++) + { + ThumbnailViewItem *pItem = mFilteredItemList[i]; + if (!pItem->mbVisible) + continue; + const tools::Rectangle& rDrawArea = pItem->getDrawArea(); + if (pItem->mbVisible && rDrawArea.Contains(aPos)) + { + rHelpRect = rDrawArea; + return pItem->getHelpText(); + } + } + + return OUString(); +} + +void ThumbnailView::AppendItem(std::unique_ptr<ThumbnailViewItem> pItem) +{ + if (maFilterFunc(pItem.get())) + { + // Save current start,end range, iterator might get invalidated + size_t nSelStartPos = 0; + ThumbnailViewItem *pSelStartItem = nullptr; + + if (mpStartSelRange != mFilteredItemList.end()) + { + pSelStartItem = *mpStartSelRange; + nSelStartPos = mpStartSelRange - mFilteredItemList.begin(); + } + + mFilteredItemList.push_back(pItem.get()); + mpStartSelRange = pSelStartItem != nullptr ? mFilteredItemList.begin() + nSelStartPos : mFilteredItemList.end(); + } + + mItemList.push_back(std::move(pItem)); +} + +void ThumbnailView::ImplInit() +{ + mnItemWidth = 0; + mnItemHeight = 0; + mnItemPadding = 0; + mnVisLines = 0; + mnLines = 0; + mnFirstLine = 0; + mnCols = 0; + mbScroll = false; + mbHasVisibleItems = false; + mbShowTooltips = false; + mbDrawMnemonics = false; + maFilterFunc = ViewFilterAll(); + + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + maFillColor = rSettings.GetFieldColor(); + maTextColor = rSettings.GetWindowTextColor(); + maHighlightColor = rSettings.GetHighlightColor(); + maHighlightTextColor = rSettings.GetHighlightTextColor(); + maSelectHighlightColor = rSettings.GetActiveColor(); + maSelectHighlightTextColor = rSettings.GetActiveTextColor(); + + mfHighlightTransparence = SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01; + + mpStartSelRange = mFilteredItemList.end(); + + UpdateColors(); + + mpItemAttrs->nMaxTextLength = 0; +} + +void ThumbnailView::UpdateColors() +{ + mpItemAttrs->aFillColor = maFillColor.getBColor(); + mpItemAttrs->aTextColor = maTextColor.getBColor(); + mpItemAttrs->aHighlightColor = maHighlightColor.getBColor(); + mpItemAttrs->aHighlightTextColor = maHighlightTextColor.getBColor(); + mpItemAttrs->aSelectHighlightColor = maSelectHighlightColor.getBColor(); + mpItemAttrs->aSelectHighlightTextColor = maSelectHighlightTextColor.getBColor(); + mpItemAttrs->fHighlightTransparence = mfHighlightTransparence; +} + +void ThumbnailView::ImplDeleteItems() +{ + const size_t n = mItemList.size(); + + for ( size_t i = 0; i < n; ++i ) + { + ThumbnailViewItem *const pItem = mItemList[i].get(); + + // deselect all current selected items and fire events + if (pItem->isSelected()) + { + pItem->setSelection(false); + maItemStateHdl.Call(pItem); + + // fire accessible event??? + } + + if ( pItem->isVisible() && ImplHasAccessibleListeners() ) + { + css::uno::Any aOldAny, aNewAny; + + aOldAny <<= pItem->GetAccessible( false ); + ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny ); + } + + mItemList[i].reset(); + } + + mItemList.clear(); + mFilteredItemList.clear(); + + mpStartSelRange = mFilteredItemList.end(); +} + +void ThumbnailView::DrawItem(ThumbnailViewItem const *pItem) +{ + if (pItem->isVisible()) + { + ::tools::Rectangle aRect = pItem->getDrawArea(); + + if (!aRect.IsEmpty()) + Invalidate(aRect); + } +} + +void ThumbnailView::OnItemDblClicked (ThumbnailViewItem*) +{ +} + +css::uno::Reference< css::accessibility::XAccessible > ThumbnailView::CreateAccessible() +{ + mxAccessible.set(new ThumbnailViewAcc(this)); + return mxAccessible; +} + +const css::uno::Reference< css::accessibility::XAccessible > & ThumbnailView::getAccessible() const +{ + return mxAccessible; +} + +void ThumbnailView::CalculateItemPositions(bool bScrollBarUsed) +{ + if (!mnItemHeight || !mnItemWidth) + return; + + Size aWinSize = GetOutputSizePixel(); + size_t nItemCount = mFilteredItemList.size(); + + // calculate window scroll ratio + float nScrollRatio; + if (bScrollBarUsed) + { + nScrollRatio = static_cast<float>(mxScrolledWindow->vadjustment_get_value()) / + static_cast<float>(mxScrolledWindow->vadjustment_get_upper() - + mxScrolledWindow->vadjustment_get_page_size()); + } + else + nScrollRatio = 0; + + // calculate ScrollBar width + tools::Long nScrBarWidth = mbAllowVScrollBar ? mxScrolledWindow->get_scroll_thickness() : 0; + + // calculate maximum number of visible columns + mnCols = static_cast<sal_uInt16>((aWinSize.Width()-nScrBarWidth) / mnItemWidth); + + if (!mnCols) + mnCols = 1; + + // calculate maximum number of visible rows + mnVisLines = static_cast<sal_uInt16>(aWinSize.Height() / mnItemHeight); + + // calculate empty space + tools::Long nHSpace = aWinSize.Width()-nScrBarWidth - mnCols*mnItemWidth; + tools::Long nVSpace = aWinSize.Height() - mnVisLines*mnItemHeight; + tools::Long nHItemSpace = nHSpace / (mnCols+1); + tools::Long nVItemSpace = mnVItemSpace; + if (nVItemSpace == -1) // auto, split up extra space to use as vertical spacing + nVItemSpace = nVSpace / (mnVisLines+1); + + // calculate maximum number of rows + // Floor( (M+N-1)/N )==Ceiling( M/N ) + mnLines = (static_cast<tools::Long>(nItemCount)+mnCols-1) / mnCols; + + if ( !mnLines ) + mnLines = 1; + + if ( mnLines <= mnVisLines ) + mnFirstLine = 0; + else if ( mnFirstLine > o3tl::make_unsigned(mnLines-mnVisLines) ) + mnFirstLine = static_cast<sal_uInt16>(mnLines-mnVisLines); + + mbHasVisibleItems = true; + + tools::Long nFullSteps = (mnLines > mnVisLines) ? mnLines - mnVisLines + 1 : 1; + + tools::Long nItemHeightOffset = mnItemHeight + nVItemSpace; + tools::Long nHiddenLines = static_cast<tools::Long>((nFullSteps - 1) * nScrollRatio); + + // calculate offsets + tools::Long nStartX = nHItemSpace; + tools::Long nStartY = nVItemSpace; + + // calculate and draw items + tools::Long x = nStartX; + tools::Long y = nStartY - ((nFullSteps - 1) * nScrollRatio - nHiddenLines) * nItemHeightOffset; + + // draw items + // Unless we are scrolling (via scrollbar) we just use the precalculated + // mnFirstLine -- our nHiddenLines calculation takes into account only + // what the user has done with the scrollbar but not any changes of selection + // using the keyboard, meaning we could accidentally hide the selected item + // if we believe the scrollbar (fdo#72287). + size_t nFirstItem = (bScrollBarUsed ? nHiddenLines : mnFirstLine) * mnCols; + size_t nLastItem = nFirstItem + (mnVisLines + 1) * mnCols; + + // If want also draw parts of items in the last line, + // then we add one more line if parts of this line are visible + + size_t nCurCount = 0; + for ( size_t i = 0; i < nItemCount; i++ ) + { + ThumbnailViewItem *const pItem = mFilteredItemList[i]; + + if ((nCurCount >= nFirstItem) && (nCurCount < nLastItem)) + { + if( !pItem->isVisible()) + { + if ( ImplHasAccessibleListeners() ) + { + css::uno::Any aOldAny, aNewAny; + + aNewAny <<= pItem->GetAccessible( false ); + ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny ); + } + + pItem->show(true); + + maItemStateHdl.Call(pItem); + } + + pItem->setDrawArea(::tools::Rectangle( Point(x,y), Size(mnItemWidth, mnItemHeight) )); + pItem->calculateItemsPosition(mnThumbnailHeight,mnItemPadding,mpItemAttrs->nMaxTextLength,mpItemAttrs.get()); + + if ( !((nCurCount+1) % mnCols) ) + { + x = nStartX; + y += mnItemHeight+nVItemSpace; + } + else + x += mnItemWidth+nHItemSpace; + } + else + { + if( pItem->isVisible()) + { + if ( ImplHasAccessibleListeners() ) + { + css::uno::Any aOldAny, aNewAny; + + aOldAny <<= pItem->GetAccessible( false ); + ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny ); + } + + pItem->show(false); + + maItemStateHdl.Call(pItem); + } + + } + + ++nCurCount; + } + + // arrange ScrollBar, set values and show it + mnLines = (nCurCount+mnCols-1)/mnCols; + + // check if scroll is needed + mbScroll = mnLines > mnVisLines; + + mxScrolledWindow->vadjustment_set_upper(mnLines * gnFineness); + mxScrolledWindow->vadjustment_set_page_size(mnVisLines * gnFineness); + if (!bScrollBarUsed) + mxScrolledWindow->vadjustment_set_value(static_cast<tools::Long>(mnFirstLine)*gnFineness); + tools::Long nPageSize = mnVisLines; + if ( nPageSize < 1 ) + nPageSize = 1; + mxScrolledWindow->vadjustment_set_page_increment(nPageSize*gnFineness); + if (mbAllowVScrollBar) + mxScrolledWindow->set_vpolicy(mbScroll ? VclPolicyType::ALWAYS : VclPolicyType::NEVER); +} + +size_t ThumbnailView::ImplGetItem( const Point& rPos ) const +{ + if ( !mbHasVisibleItems ) + { + return THUMBNAILVIEW_ITEM_NOTFOUND; + } + + for (size_t i = 0; i < mFilteredItemList.size(); ++i) + { + if (mFilteredItemList[i]->isVisible() && mFilteredItemList[i]->getDrawArea().Contains(rPos)) + return i; + } + + return THUMBNAILVIEW_ITEM_NOTFOUND; +} + +ThumbnailViewItem* ThumbnailView::ImplGetItem( size_t nPos ) +{ + return ( nPos < mFilteredItemList.size() ) ? mFilteredItemList[nPos] : nullptr; +} + +sal_uInt16 ThumbnailView::ImplGetVisibleItemCount() const +{ + sal_uInt16 nRet = 0; + const size_t nItemCount = mItemList.size(); + + for ( size_t n = 0; n < nItemCount; ++n ) + { + if ( mItemList[n]->isVisible() ) + ++nRet; + } + + return nRet; +} + +ThumbnailViewItem* ThumbnailView::ImplGetVisibleItem( sal_uInt16 nVisiblePos ) +{ + const size_t nItemCount = mItemList.size(); + + for ( size_t n = 0; n < nItemCount; ++n ) + { + ThumbnailViewItem *const pItem = mItemList[n].get(); + + if ( pItem->isVisible() && !nVisiblePos-- ) + return pItem; + } + + return nullptr; +} + +void ThumbnailView::ImplFireAccessibleEvent( short nEventId, const css::uno::Any& rOldValue, const css::uno::Any& rNewValue ) +{ + ThumbnailViewAcc* pAcc = ThumbnailViewAcc::getImplementation(mxAccessible); + + if( pAcc ) + pAcc->FireAccessibleEvent( nEventId, rOldValue, rNewValue ); +} + +bool ThumbnailView::ImplHasAccessibleListeners() const +{ + ThumbnailViewAcc* pAcc = ThumbnailViewAcc::getImplementation(mxAccessible); + return( pAcc && pAcc->HasAccessibleListeners() ); +} + +IMPL_LINK_NOARG(ThumbnailView, ImplScrollHdl, weld::ScrolledWindow&, void) +{ + CalculateItemPositions(true); + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); +} + +bool ThumbnailView::KeyInput( const KeyEvent& rKEvt ) +{ + bool bHandled = true; + + // Get the last selected item in the list + size_t nLastPos = 0; + bool bFoundLast = false; + for ( tools::Long i = mFilteredItemList.size() - 1; !bFoundLast && i >= 0; --i ) + { + ThumbnailViewItem* pItem = mFilteredItemList[i]; + if ( pItem->isSelected() ) + { + nLastPos = i; + bFoundLast = true; + } + } + + bool bValidRange = false; + bool bHasSelRange = mpStartSelRange != mFilteredItemList.end(); + size_t nNextPos = nLastPos; + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + ThumbnailViewItem* pNext = nullptr; + + if (aKeyCode.IsShift() && bHasSelRange) + { + //If the last element selected is the start range position + //search for the first selected item + size_t nSelPos = mpStartSelRange - mFilteredItemList.begin(); + + if (nLastPos == nSelPos) + { + while (nLastPos && mFilteredItemList[nLastPos-1]->isSelected()) + --nLastPos; + } + } + + switch ( aKeyCode.GetCode() ) + { + case KEY_RIGHT: + if (!mFilteredItemList.empty()) + { + if ( bFoundLast && nLastPos + 1 < mFilteredItemList.size() ) + { + bValidRange = true; + nNextPos = nLastPos + 1; + } + + pNext = mFilteredItemList[nNextPos]; + } + break; + case KEY_LEFT: + if (!mFilteredItemList.empty()) + { + if ( nLastPos > 0 ) + { + bValidRange = true; + nNextPos = nLastPos - 1; + } + + pNext = mFilteredItemList[nNextPos]; + } + break; + case KEY_DOWN: + if (!mFilteredItemList.empty()) + { + if ( bFoundLast ) + { + //If we are in the second last row just go the one in + //the row below, if there's not row below just go to the + //last item but for the last row don't do anything. + if ( nLastPos + mnCols < mFilteredItemList.size( ) ) + { + bValidRange = true; + nNextPos = nLastPos + mnCols; + } + else + { + int curRow = nLastPos/mnCols; + + if (curRow < mnLines-1) + nNextPos = mFilteredItemList.size()-1; + } + } + + pNext = mFilteredItemList[nNextPos]; + } + break; + case KEY_UP: + if (!mFilteredItemList.empty()) + { + if ( nLastPos >= mnCols ) + { + bValidRange = true; + nNextPos = nLastPos - mnCols; + } + + pNext = mFilteredItemList[nNextPos]; + } + break; + case KEY_RETURN: + { + if ( bFoundLast ) + OnItemDblClicked( mFilteredItemList[nLastPos] ); + } + [[fallthrough]]; + default: + bHandled = CustomWidgetController::KeyInput(rKEvt); + } + + if ( pNext ) + { + if (aKeyCode.IsShift() && bValidRange) + { + std::pair<size_t,size_t> aRange; + size_t nSelPos = mpStartSelRange - mFilteredItemList.begin(); + + if (nLastPos < nSelPos) + { + if (nNextPos > nLastPos) + { + if ( nNextPos > nSelPos) + aRange = std::make_pair(nLastPos,nNextPos); + else + aRange = std::make_pair(nLastPos,nNextPos-1); + } + else + aRange = std::make_pair(nNextPos,nLastPos-1); + } + else if (nLastPos == nSelPos) + { + if (nNextPos > nLastPos) + aRange = std::make_pair(nLastPos+1,nNextPos); + else + aRange = std::make_pair(nNextPos,nLastPos-1); + } + else + { + if (nNextPos > nLastPos) + aRange = std::make_pair(nLastPos+1,nNextPos); + else + { + if ( nNextPos < nSelPos) + aRange = std::make_pair(nNextPos,nLastPos); + else + aRange = std::make_pair(nNextPos+1,nLastPos); + } + } + + for (size_t i = aRange.first; i <= aRange.second; ++i) + { + if (i != nSelPos) + { + ThumbnailViewItem *pCurItem = mFilteredItemList[i]; + + pCurItem->setSelection(!pCurItem->isSelected()); + + DrawItem(pCurItem); + + maItemStateHdl.Call(pCurItem); + } + } + } + else if (!aKeyCode.IsShift()) + { + deselectItems(); + SelectItem(pNext->mnId); + + //Mark it as the selection range start position + mpStartSelRange = mFilteredItemList.begin() + nNextPos; + } + + MakeItemVisible(pNext->mnId); + } + return bHandled; +} + +void ThumbnailView::MakeItemVisible( sal_uInt16 nItemId ) +{ + // Get the item row + size_t nPos = 0; + bool bFound = false; + for ( size_t i = 0; !bFound && i < mFilteredItemList.size(); ++i ) + { + ThumbnailViewItem* pItem = mFilteredItemList[i]; + if ( pItem->mnId == nItemId ) + { + nPos = i; + bFound = true; + } + } + sal_uInt16 nRow = mnCols ? nPos / mnCols : 0; + + // Move the visible rows as little as possible to include that one + if ( nRow < mnFirstLine ) + mnFirstLine = nRow; + else if ( nRow > mnFirstLine + mnVisLines ) + mnFirstLine = nRow - mnVisLines; + + CalculateItemPositions(); + Invalidate(); +} + +bool ThumbnailView::MouseButtonDown( const MouseEvent& rMEvt ) +{ + GrabFocus(); + + if (!rMEvt.IsLeft()) + { + return CustomWidgetController::MouseButtonDown( rMEvt ); + } + + size_t nPos = ImplGetItem(rMEvt.GetPosPixel()); + ThumbnailViewItem* pItem = ImplGetItem(nPos); + + if ( !pItem ) + { + deselectItems(); + return CustomWidgetController::MouseButtonDown( rMEvt ); + } + + if ( rMEvt.GetClicks() == 2 ) + { + OnItemDblClicked(pItem); + return true; + } + + if(rMEvt.GetClicks() == 1) + { + if (rMEvt.IsMod1()) + { + //Keep selected item group state and just invert current desired one state + pItem->setSelection(!pItem->isSelected()); + + //This one becomes the selection range start position if it changes its state to selected otherwise resets it + mpStartSelRange = pItem->isSelected() ? mFilteredItemList.begin() + nPos : mFilteredItemList.end(); + } + else if (rMEvt.IsShift() && mpStartSelRange != mFilteredItemList.end()) + { + std::pair<size_t,size_t> aNewRange; + aNewRange.first = mpStartSelRange - mFilteredItemList.begin(); + aNewRange.second = nPos; + + if (aNewRange.first > aNewRange.second) + std::swap(aNewRange.first,aNewRange.second); + + //Deselect the ones outside of it + for (size_t i = 0, n = mFilteredItemList.size(); i < n; ++i) + { + ThumbnailViewItem *pCurItem = mFilteredItemList[i]; + + if (pCurItem->isSelected() && (i < aNewRange.first || i > aNewRange.second)) + { + pCurItem->setSelection(false); + + DrawItem(pCurItem); + + maItemStateHdl.Call(pCurItem); + } + } + + size_t nSelPos = mpStartSelRange - mFilteredItemList.begin(); + + //Select the items between start range and the selected item + if (nSelPos != nPos) + { + int dir = nSelPos < nPos ? 1 : -1; + size_t nCurPos = nSelPos + dir; + + while (nCurPos != nPos) + { + ThumbnailViewItem *pCurItem = mFilteredItemList[nCurPos]; + + if (!pCurItem->isSelected()) + { + pCurItem->setSelection(true); + + DrawItem(pCurItem); + + maItemStateHdl.Call(pCurItem); + } + + nCurPos += dir; + } + } + + pItem->setSelection(true); + } + else + { + //If we got a group of selected items deselect the rest and only keep the desired one + //mark items as not selected to not fire unnecessary change state events. + pItem->setSelection(false); + deselectItems(); + pItem->setSelection(true); + + //Mark as initial selection range position and reset end one + mpStartSelRange = mFilteredItemList.begin() + nPos; + } + + if (!pItem->isHighlighted()) + DrawItem(pItem); + + maItemStateHdl.Call(pItem); + + //fire accessible event?? + } + return true; +} + +void ThumbnailView::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + weld::SetPointFont(rDevice, pDrawingArea->get_font()); + mpItemAttrs->aFontAttr = getFontAttributeFromVclFont(mpItemAttrs->aFontSize, rDevice.GetFont(), false, true); + + SetOutputSizePixel(pDrawingArea->get_preferred_size()); +} + +void ThumbnailView::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& /*rRect*/) +{ + rRenderContext.Push(vcl::PushFlags::ALL); + + rRenderContext.SetTextFillColor(); + rRenderContext.SetBackground(maFillColor); + + size_t nItemCount = mItemList.size(); + + // Draw background + drawinglayer::primitive2d::Primitive2DContainer aSeq(1); + aSeq[0] = drawinglayer::primitive2d::Primitive2DReference( + new PolyPolygonColorPrimitive2D( + B2DPolyPolygon( ::tools::Polygon(::tools::Rectangle(Point(), GetOutputSizePixel()), 0, 0).getB2DPolygon()), + maFillColor.getBColor())); + + // Create the processor and process the primitives + const drawinglayer::geometry::ViewInformation2D aNewViewInfos; + + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor( + drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(rRenderContext, aNewViewInfos)); + pProcessor->process(aSeq); + + // draw items + for (size_t i = 0; i < nItemCount; i++) + { + ThumbnailViewItem *const pItem = mItemList[i].get(); + if (!pItem->isVisible()) + continue; + pItem->Paint(pProcessor.get(), mpItemAttrs.get()); + } + + rRenderContext.Pop(); +} + +void ThumbnailView::GetFocus() +{ + if (mbSelectOnFocus) + { + // Select the first item if nothing selected + int nSelected = -1; + for (size_t i = 0, n = mItemList.size(); i < n && nSelected == -1; ++i) + { + if (mItemList[i]->isSelected()) + nSelected = i; + } + + if (nSelected == -1 && !mItemList.empty()) + { + ThumbnailViewItem* pFirst = nullptr; + if (!mFilteredItemList.empty()) { + pFirst = mFilteredItemList[0]; + } else { + pFirst = mItemList[0].get(); + } + + SelectItem(pFirst->mnId); + } + } + + // Tell the accessible object that we got the focus. + ThumbnailViewAcc* pAcc = ThumbnailViewAcc::getImplementation(mxAccessible); + if( pAcc ) + pAcc->GetFocus(); + + CustomWidgetController::GetFocus(); +} + +void ThumbnailView::LoseFocus() +{ + CustomWidgetController::LoseFocus(); + + // Tell the accessible object that we lost the focus. + ThumbnailViewAcc* pAcc = ThumbnailViewAcc::getImplementation(mxAccessible); + if( pAcc ) + pAcc->LoseFocus(); +} + +void ThumbnailView::Resize() +{ + CustomWidgetController::Resize(); + CalculateItemPositions(); + + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); +} + +void ThumbnailView::RemoveItem( sal_uInt16 nItemId ) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == THUMBNAILVIEW_ITEM_NOTFOUND ) + return; + + if ( nPos < mFilteredItemList.size() ) { + + // keep it alive until after we have deleted it from the filter item list + std::unique_ptr<ThumbnailViewItem> xKeepAliveViewItem; + + // delete item from the thumbnail list + for (auto it = mItemList.begin(); it != mItemList.end(); ++it) + { + if ((*it)->mnId == nItemId) + { + xKeepAliveViewItem = std::move(*it); + mItemList.erase(it); + break; + } + } + + // delete item from the filter item list + ThumbnailValueItemList::iterator it = mFilteredItemList.begin(); + ::std::advance( it, nPos ); + + if ((*it)->isSelected()) + { + (*it)->setSelection(false); + maItemStateHdl.Call(*it); + } + + mFilteredItemList.erase( it ); + mpStartSelRange = mFilteredItemList.end(); + } + + CalculateItemPositions(); + + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); +} + +void ThumbnailView::Clear() +{ + ImplDeleteItems(); + + // reset variables + mnFirstLine = 0; + + CalculateItemPositions(); + + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); +} + +void ThumbnailView::updateItems (std::vector<std::unique_ptr<ThumbnailViewItem>> items) +{ + ImplDeleteItems(); + + // reset variables + mnFirstLine = 0; + + mItemList = std::move(items); + + filterItems(maFilterFunc); +} + +size_t ThumbnailView::GetItemPos( sal_uInt16 nItemId ) const +{ + for ( size_t i = 0, n = mFilteredItemList.size(); i < n; ++i ) { + if ( mFilteredItemList[i]->mnId == nItemId ) { + return i; + } + } + return THUMBNAILVIEW_ITEM_NOTFOUND; +} + +sal_uInt16 ThumbnailView::GetItemId( size_t nPos ) const +{ + return ( nPos < mFilteredItemList.size() ) ? mFilteredItemList[nPos]->mnId : 0 ; +} + +sal_uInt16 ThumbnailView::GetItemId( const Point& rPos ) const +{ + size_t nItemPos = ImplGetItem( rPos ); + if ( nItemPos != THUMBNAILVIEW_ITEM_NOTFOUND ) + return GetItemId( nItemPos ); + + return 0; +} + +sal_uInt16 ThumbnailView::getNextItemId() const +{ + return mItemList.empty() ? 1 : mItemList.back()->mnId + 1; +} + +void ThumbnailView::setItemMaxTextLength(sal_uInt32 nLength) +{ + mpItemAttrs->nMaxTextLength = nLength; +} + +void ThumbnailView::setItemDimensions(tools::Long itemWidth, tools::Long thumbnailHeight, tools::Long displayHeight, int itemPadding) +{ + mnItemWidth = itemWidth + 2*itemPadding; + mnThumbnailHeight = thumbnailHeight; + mnDisplayHeight = displayHeight; + mnItemPadding = itemPadding; + mnItemHeight = mnDisplayHeight + mnThumbnailHeight + 2*itemPadding; +} + +void ThumbnailView::SelectItem( sal_uInt16 nItemId ) +{ + size_t nItemPos = GetItemPos( nItemId ); + if ( nItemPos == THUMBNAILVIEW_ITEM_NOTFOUND ) + return; + + ThumbnailViewItem* pItem = mFilteredItemList[nItemPos]; + if (pItem->isSelected()) + return; + + pItem->setSelection(true); + maItemStateHdl.Call(pItem); + + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + bool bNewOut = IsReallyVisible() && IsUpdateMode(); + + // if necessary scroll to the visible area + if (mbScroll && nItemId && mnCols) + { + sal_uInt16 nNewLine = static_cast<sal_uInt16>(nItemPos / mnCols); + if ( nNewLine < mnFirstLine ) + { + mnFirstLine = nNewLine; + } + else if ( mnVisLines != 0 && nNewLine > o3tl::make_unsigned(mnFirstLine+mnVisLines-1) ) + { + mnFirstLine = static_cast<sal_uInt16>(nNewLine-mnVisLines+1); + } + } + + if ( bNewOut ) + { + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); + } + + if( !ImplHasAccessibleListeners() ) + return; + + // focus event (select) + ThumbnailViewItemAcc* pItemAcc = ThumbnailViewItemAcc::getImplementation( pItem->GetAccessible( false ) ); + + if( pItemAcc ) + { + css::uno::Any aOldAny, aNewAny; + aNewAny <<= css::uno::Reference< css::uno::XInterface >( + static_cast< ::cppu::OWeakObject* >( pItemAcc )); + ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOldAny, aNewAny ); + } + + // selection event + css::uno::Any aOldAny, aNewAny; + ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::SELECTION_CHANGED, aOldAny, aNewAny ); +} + +bool ThumbnailView::IsItemSelected( sal_uInt16 nItemId ) const +{ + size_t nItemPos = GetItemPos( nItemId ); + if ( nItemPos == THUMBNAILVIEW_ITEM_NOTFOUND ) + return false; + + ThumbnailViewItem* pItem = mFilteredItemList[nItemPos]; + return pItem->isSelected(); +} + +void ThumbnailView::deselectItems() +{ + for (std::unique_ptr<ThumbnailViewItem>& p : mItemList) + { + if (p->isSelected()) + { + p->setSelection(false); + + maItemStateHdl.Call(p.get()); + } + } + + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); +} + +void ThumbnailView::ShowTooltips( bool bShowTooltips ) +{ + mbShowTooltips = bShowTooltips; +} + +void ThumbnailView::DrawMnemonics( bool bDrawMnemonics ) +{ + mbDrawMnemonics = bDrawMnemonics; +} + +void ThumbnailView::filterItems(const std::function<bool (const ThumbnailViewItem*)> &func) +{ + mnFirstLine = 0; // start at the top of the list instead of the current position + maFilterFunc = func; + + size_t nSelPos = 0; + bool bHasSelRange = false; + ThumbnailViewItem *curSel = mpStartSelRange != mFilteredItemList.end() ? *mpStartSelRange : nullptr; + + mFilteredItemList.clear(); + + for (size_t i = 0, n = mItemList.size(); i < n; ++i) + { + ThumbnailViewItem *const pItem = mItemList[i].get(); + + if (maFilterFunc(pItem)) + { + if (curSel == pItem) + { + nSelPos = i; + bHasSelRange = true; + } + + mFilteredItemList.push_back(pItem); + } + else + { + if( pItem->isVisible()) + { + if ( ImplHasAccessibleListeners() ) + { + css::uno::Any aOldAny, aNewAny; + + aOldAny <<= pItem->GetAccessible( false ); + ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny ); + } + + pItem->show(false); + pItem->setSelection(false); + + maItemStateHdl.Call(pItem); + } + } + } + + mpStartSelRange = bHasSelRange ? mFilteredItemList.begin() + nSelPos : mFilteredItemList.end(); + CalculateItemPositions(); + + Invalidate(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/thumbnailviewacc.cxx b/sfx2/source/control/thumbnailviewacc.cxx new file mode 100644 index 000000000..4492980cc --- /dev/null +++ b/sfx2/source/control/thumbnailviewacc.cxx @@ -0,0 +1,874 @@ +/* -*- 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 "thumbnailviewacc.hxx" + +#include <comphelper/servicehelper.hxx> +#include <sfx2/thumbnailview.hxx> +#include <sfx2/thumbnailviewitem.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +using namespace ::com::sun::star; + +ThumbnailViewAcc::ThumbnailViewAcc( ThumbnailView* pParent ) : + mpParent( pParent ) +{ +} + +ThumbnailViewAcc::~ThumbnailViewAcc() +{ +} + +const uno::Sequence< sal_Int8 >& ThumbnailViewAcc::getUnoTunnelId() +{ + static const comphelper::UnoIdInit theSfxValueSetAccUnoTunnelId; + return theSfxValueSetAccUnoTunnelId.getSeq(); +} + +ThumbnailViewAcc* ThumbnailViewAcc::getImplementation( const uno::Reference< uno::XInterface >& rxData ) + noexcept +{ + try + { + return comphelper::getFromUnoTunnel<ThumbnailViewAcc>(rxData); + } + catch(const css::uno::Exception&) + { + return nullptr; + } +} + +uno::Reference< accessibility::XAccessibleContext > SAL_CALL ThumbnailViewAcc::getAccessibleContext() +{ + ThrowIfDisposed(); + return this; +} + +sal_Int32 SAL_CALL ThumbnailViewAcc::getAccessibleChildCount() +{ + const SolarMutexGuard aSolarGuard; + ThrowIfDisposed(); + + sal_Int32 nCount = mpParent->ImplGetVisibleItemCount(); + return nCount; +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ThumbnailViewAcc::getAccessibleChild( sal_Int32 i ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + ThumbnailViewItem* pItem = getItem (sal::static_int_cast< sal_uInt16 >(i)); + + if( !pItem ) + throw lang::IndexOutOfBoundsException(); + + uno::Reference< accessibility::XAccessible > xRet = pItem->GetAccessible( /*bIsTransientChildrenDisabled*/false ); + return xRet; +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ThumbnailViewAcc::getAccessibleParent() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + return mpParent->GetDrawingArea()->get_accessible_parent(); +} + +sal_Int32 SAL_CALL ThumbnailViewAcc::getAccessibleIndexInParent() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + + // -1 for child not found/no parent (according to specification) + sal_Int32 nRet = -1; + + uno::Reference<accessibility::XAccessible> xParent(getAccessibleParent()); + if (!xParent) + return nRet; + + try + { + uno::Reference<accessibility::XAccessibleContext> xParentContext(xParent->getAccessibleContext()); + + // iterate over parent's children and search for this object + if ( xParentContext.is() ) + { + sal_Int32 nChildCount = xParentContext->getAccessibleChildCount(); + for ( sal_Int32 nChild = 0; ( nChild < nChildCount ) && ( -1 == nRet ); ++nChild ) + { + uno::Reference<XAccessible> xChild(xParentContext->getAccessibleChild(nChild)); + if ( xChild.get() == this ) + nRet = nChild; + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sfx", "OAccessibleContextHelper::getAccessibleIndexInParent" ); + } + + return nRet; +} + +sal_Int16 SAL_CALL ThumbnailViewAcc::getAccessibleRole() +{ + ThrowIfDisposed(); + // #i73746# As the Java Access Bridge (v 2.0.1) uses "managesDescendants" + // always if the role is LIST, we need a different role in this case + return accessibility::AccessibleRole::LIST; +} + +OUString SAL_CALL ThumbnailViewAcc::getAccessibleDescription() +{ + ThrowIfDisposed(); + return "ThumbnailView"; +} + +OUString SAL_CALL ThumbnailViewAcc::getAccessibleName() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + OUString aRet; + + if (mpParent) + { + aRet = mpParent->GetAccessibleName(); + } + + return aRet; +} + +uno::Reference< accessibility::XAccessibleRelationSet > SAL_CALL ThumbnailViewAcc::getAccessibleRelationSet() +{ + ThrowIfDisposed(); + return uno::Reference< accessibility::XAccessibleRelationSet >(); +} + +uno::Reference< accessibility::XAccessibleStateSet > SAL_CALL ThumbnailViewAcc::getAccessibleStateSet() +{ + ThrowIfDisposed(); + rtl::Reference<::utl::AccessibleStateSetHelper> pStateSet = new ::utl::AccessibleStateSetHelper(); + + // Set some states. + pStateSet->AddState (accessibility::AccessibleStateType::ENABLED); + pStateSet->AddState (accessibility::AccessibleStateType::SENSITIVE); + pStateSet->AddState (accessibility::AccessibleStateType::SHOWING); + pStateSet->AddState (accessibility::AccessibleStateType::VISIBLE); + pStateSet->AddState (accessibility::AccessibleStateType::MANAGES_DESCENDANTS); + pStateSet->AddState (accessibility::AccessibleStateType::FOCUSABLE); + + return pStateSet; +} + +lang::Locale SAL_CALL ThumbnailViewAcc::getLocale() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xParent( getAccessibleParent() ); + lang::Locale aRet( "", "", "" ); + + if( xParent.is() ) + { + uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + + if( xParentContext.is() ) + aRet = xParentContext->getLocale (); + } + + return aRet; +} + +void SAL_CALL ThumbnailViewAcc::addAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + ThrowIfDisposed(); + std::unique_lock aGuard (m_aMutex); + + if( !rxListener.is() ) + return; + + bool bFound = false; + + for (auto const& eventListener : mxEventListeners) + { + if( eventListener == rxListener ) + { + bFound = true; + break; + } + } + + if (!bFound) + mxEventListeners.push_back( rxListener ); +} + +void SAL_CALL ThumbnailViewAcc::removeAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + ThrowIfDisposed(); + std::unique_lock aGuard (m_aMutex); + + if( rxListener.is() ) + { + std::vector< uno::Reference< accessibility::XAccessibleEventListener > >::iterator aIter = + std::find(mxEventListeners.begin(), mxEventListeners.end(), rxListener); + + if (aIter != mxEventListeners.end()) + mxEventListeners.erase( aIter ); + } +} + +sal_Bool SAL_CALL ThumbnailViewAcc::containsPoint( const awt::Point& aPoint ) +{ + ThrowIfDisposed(); + const awt::Rectangle aRect( getBounds() ); + const Point aSize( aRect.Width, aRect.Height ); + const Point aNullPoint, aTestPoint( aPoint.X, aPoint.Y ); + + return tools::Rectangle( aNullPoint, aSize ).Contains( aTestPoint ); +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ThumbnailViewAcc::getAccessibleAtPoint( const awt::Point& aPoint ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + const sal_uInt16 nItemId = mpParent->GetItemId( Point( aPoint.X, aPoint.Y ) ); + uno::Reference< accessibility::XAccessible > xRet; + + if ( nItemId ) + { + const size_t nItemPos = mpParent->GetItemPos( nItemId ); + + if( THUMBNAILVIEW_ITEM_NONEITEM != nItemPos ) + { + ThumbnailViewItem *const pItem = mpParent->mFilteredItemList[nItemPos]; + xRet = pItem->GetAccessible( /*bIsTransientChildrenDisabled*/false ); + } + } + + return xRet; +} + +awt::Rectangle SAL_CALL ThumbnailViewAcc::getBounds() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + const Point aOutPos; + const Size aOutSize( mpParent->GetOutputSizePixel() ); + awt::Rectangle aRet; + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + + return aRet; +} + +awt::Point SAL_CALL ThumbnailViewAcc::getLocation() +{ + ThrowIfDisposed(); + const awt::Rectangle aRect( getBounds() ); + awt::Point aRet; + + aRet.X = aRect.X; + aRet.Y = aRect.Y; + + return aRet; +} + +awt::Point SAL_CALL ThumbnailViewAcc::getLocationOnScreen() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + awt::Point aScreenLoc(0, 0); + + uno::Reference<accessibility::XAccessible> xParent(getAccessibleParent()); + if (xParent) + { + uno::Reference<accessibility::XAccessibleContext> xParentContext(xParent->getAccessibleContext()); + uno::Reference<accessibility::XAccessibleComponent> xParentComponent(xParentContext, css::uno::UNO_QUERY); + OSL_ENSURE( xParentComponent.is(), "ThumbnailViewAcc::getLocationOnScreen: no parent component!" ); + if ( xParentComponent.is() ) + { + awt::Point aParentScreenLoc( xParentComponent->getLocationOnScreen() ); + awt::Point aOwnRelativeLoc( getLocation() ); + aScreenLoc.X = aParentScreenLoc.X + aOwnRelativeLoc.X; + aScreenLoc.Y = aParentScreenLoc.Y + aOwnRelativeLoc.Y; + } + } + + return aScreenLoc; +} + +awt::Size SAL_CALL ThumbnailViewAcc::getSize() +{ + ThrowIfDisposed(); + const awt::Rectangle aRect( getBounds() ); + awt::Size aRet; + + aRet.Width = aRect.Width; + aRet.Height = aRect.Height; + + return aRet; +} + +void SAL_CALL ThumbnailViewAcc::grabFocus() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + mpParent->GrabFocus(); +} + +sal_Int32 SAL_CALL ThumbnailViewAcc::getForeground( ) +{ + ThrowIfDisposed(); + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowTextColor(); + return static_cast<sal_Int32>(nColor); +} + +sal_Int32 SAL_CALL ThumbnailViewAcc::getBackground( ) +{ + ThrowIfDisposed(); + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowColor(); + return static_cast<sal_Int32>(nColor); +} + +void SAL_CALL ThumbnailViewAcc::selectAccessibleChild( sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + ThumbnailViewItem* pItem = getItem (sal::static_int_cast< sal_uInt16 >(nChildIndex)); + + if(pItem == nullptr) + throw lang::IndexOutOfBoundsException(); + + mpParent->SelectItem( pItem->mnId ); +} + +sal_Bool SAL_CALL ThumbnailViewAcc::isAccessibleChildSelected( sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + ThumbnailViewItem* pItem = getItem (sal::static_int_cast< sal_uInt16 >(nChildIndex)); + + if (pItem == nullptr) + throw lang::IndexOutOfBoundsException(); + + return mpParent->IsItemSelected( pItem->mnId ); +} + +void SAL_CALL ThumbnailViewAcc::clearAccessibleSelection() +{ + ThrowIfDisposed(); +} + +void SAL_CALL ThumbnailViewAcc::selectAllAccessibleChildren() +{ + ThrowIfDisposed(); + // unsupported due to single selection only +} + +sal_Int32 SAL_CALL ThumbnailViewAcc::getSelectedAccessibleChildCount() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + sal_Int32 nRet = 0; + + for( sal_uInt16 i = 0, nCount = getItemCount(); i < nCount; i++ ) + { + ThumbnailViewItem* pItem = getItem (i); + + if( pItem && mpParent->IsItemSelected( pItem->mnId ) ) + ++nRet; + } + + return nRet; +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ThumbnailViewAcc::getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xRet; + + for( sal_uInt16 i = 0, nCount = getItemCount(), nSel = 0; ( i < nCount ) && !xRet.is(); i++ ) + { + ThumbnailViewItem* pItem = getItem(i); + + if( pItem && mpParent->IsItemSelected( pItem->mnId ) && ( nSelectedChildIndex == static_cast< sal_Int32 >( nSel++ ) ) ) + xRet = pItem->GetAccessible( /*bIsTransientChildrenDisabled*/false ); + } + + return xRet; +} + +void SAL_CALL ThumbnailViewAcc::deselectAccessibleChild( sal_Int32 ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + // Because of the single selection we can reset the whole selection when + // the specified child is currently selected. +//FIXME TODO if (isAccessibleChildSelected(nChildIndex)) +//FIXME TODO ; +} + +sal_Int64 SAL_CALL ThumbnailViewAcc::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl(rId, this); +} + +void ThumbnailViewAcc::disposing(std::unique_lock<std::mutex>& rGuard) +{ + ::std::vector<uno::Reference<accessibility::XAccessibleEventListener> > aListenerListCopy; + + // unlock because we need to take solar and the lock mutex in the correct order + rGuard.unlock(); + { + const SolarMutexGuard aSolarGuard; + std::unique_lock aGuard (m_aMutex); + + // Reset the pointer to the parent. It has to be the one who has + // disposed us because he is dying. + mpParent = nullptr; + + if (mxEventListeners.empty()) + return; + + // Make a copy of the list and clear the original. + aListenerListCopy = std::move(mxEventListeners); + } + + // Inform all listeners that this objects is disposing. + lang::EventObject aEvent (static_cast<accessibility::XAccessible*>(this)); + for (auto const& listener : aListenerListCopy) + { + try + { + listener->disposing (aEvent); + } + catch(const uno::Exception&) + { + // Ignore exceptions. + } + } +} + +sal_uInt16 ThumbnailViewAcc::getItemCount() const +{ + return mpParent->ImplGetVisibleItemCount(); +} + +ThumbnailViewItem* ThumbnailViewAcc::getItem (sal_uInt16 nIndex) const +{ + return mpParent->ImplGetVisibleItem (nIndex); +} + +void ThumbnailViewAcc::ThrowIfDisposed() +{ + if (m_bDisposed) + { + SAL_WARN("sfx", "Calling disposed object. Throwing exception:"); + throw lang::DisposedException ( + "object has been already disposed", + static_cast<uno::XWeak*>(this)); + } + else + { + DBG_ASSERT (mpParent!=nullptr, "ValueSetAcc not disposed but mpParent == NULL"); + } +} + +ThumbnailViewItemAcc::ThumbnailViewItemAcc( ThumbnailViewItem* pParent, bool bIsTransientChildrenDisabled ) : + mpParent( pParent ), + mbIsTransientChildrenDisabled( bIsTransientChildrenDisabled ) +{ +} + +ThumbnailViewItemAcc::~ThumbnailViewItemAcc() +{ +} + +void ThumbnailViewItemAcc::ParentDestroyed() +{ + std::scoped_lock aGuard( maMutex ); + mpParent = nullptr; +} + +void ThumbnailViewAcc::FireAccessibleEvent( short nEventId, const uno::Any& rOldValue, const uno::Any& rNewValue ) +{ + if( !nEventId ) + return; + + ::std::vector< uno::Reference< accessibility::XAccessibleEventListener > > aTmpListeners( mxEventListeners ); + accessibility::AccessibleEventObject aEvtObject; + + aEvtObject.EventId = nEventId; + aEvtObject.Source = static_cast<uno::XWeak*>(this); + aEvtObject.NewValue = rNewValue; + aEvtObject.OldValue = rOldValue; + + for (auto const& tmpListener : aTmpListeners) + { + try + { + tmpListener->notifyEvent( aEvtObject ); + } + catch(const uno::Exception&) + { + } + } +} + +const uno::Sequence< sal_Int8 >& ThumbnailViewItemAcc::getUnoTunnelId() +{ + static const comphelper::UnoIdInit theValueItemAccUnoTunnelId; + return theValueItemAccUnoTunnelId.getSeq(); +} + +ThumbnailViewItemAcc* ThumbnailViewItemAcc::getImplementation( const uno::Reference< uno::XInterface >& rxData ) + noexcept +{ + try + { + return comphelper::getFromUnoTunnel<ThumbnailViewItemAcc>(rxData); + } + catch(const css::uno::Exception&) + { + return nullptr; + } +} + +void ThumbnailViewAcc::GetFocus() +{ + // Broadcast the state change. + css::uno::Any aOldState, aNewState; + aNewState <<= css::accessibility::AccessibleStateType::FOCUSED; + FireAccessibleEvent( + css::accessibility::AccessibleEventId::STATE_CHANGED, + aOldState, aNewState); +} + +void ThumbnailViewAcc::LoseFocus() +{ + // Broadcast the state change. + css::uno::Any aOldState, aNewState; + aOldState <<= css::accessibility::AccessibleStateType::FOCUSED; + FireAccessibleEvent( + css::accessibility::AccessibleEventId::STATE_CHANGED, + aOldState, aNewState); +} + +uno::Reference< accessibility::XAccessibleContext > SAL_CALL ThumbnailViewItemAcc::getAccessibleContext() +{ + return this; +} + +sal_Int32 SAL_CALL ThumbnailViewItemAcc::getAccessibleChildCount() +{ + return 0; +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ThumbnailViewItemAcc::getAccessibleChild( sal_Int32 ) +{ + throw lang::IndexOutOfBoundsException(); +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ThumbnailViewItemAcc::getAccessibleParent() +{ + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xRet; + + if( mpParent ) + xRet = mpParent->mrParent.getAccessible(); + + return xRet; +} + +sal_Int32 SAL_CALL ThumbnailViewItemAcc::getAccessibleIndexInParent() +{ + const SolarMutexGuard aSolarGuard; + // The index defaults to -1 to indicate the child does not belong to its + // parent. + sal_Int32 nIndexInParent = -1; + + if( mpParent ) + { + bool bDone = false; + + sal_uInt16 nCount = mpParent->mrParent.ImplGetVisibleItemCount(); + ThumbnailViewItem* pItem; + for (sal_uInt16 i=0; i<nCount && !bDone; i++) + { + // Guard the retrieval of the i-th child with a try/catch block + // just in case the number of children changes in the meantime. + try + { + pItem = mpParent->mrParent.ImplGetVisibleItem (i); + } + catch (const lang::IndexOutOfBoundsException&) + { + pItem = nullptr; + } + + // Do not create an accessible object for the test. + if (pItem != nullptr && pItem->mxAcc.is()) + if (pItem->GetAccessible( mbIsTransientChildrenDisabled ).get() == this ) + { + nIndexInParent = i; + bDone = true; + } + } + } + + return nIndexInParent; +} + +sal_Int16 SAL_CALL ThumbnailViewItemAcc::getAccessibleRole() +{ + return accessibility::AccessibleRole::LIST_ITEM; +} + +OUString SAL_CALL ThumbnailViewItemAcc::getAccessibleDescription() +{ + return OUString(); +} + +OUString SAL_CALL ThumbnailViewItemAcc::getAccessibleName() +{ + const SolarMutexGuard aSolarGuard; + OUString aRet; + + if( mpParent ) + { + aRet = mpParent->maTitle; + + if( aRet.isEmpty() ) + { + aRet = "Item " + OUString::number(static_cast<sal_Int32>(mpParent->mnId)); + } + } + + return aRet; +} + +uno::Reference< accessibility::XAccessibleRelationSet > SAL_CALL ThumbnailViewItemAcc::getAccessibleRelationSet() +{ + return uno::Reference< accessibility::XAccessibleRelationSet >(); +} + +uno::Reference< accessibility::XAccessibleStateSet > SAL_CALL ThumbnailViewItemAcc::getAccessibleStateSet() +{ + const SolarMutexGuard aSolarGuard; + rtl::Reference<::utl::AccessibleStateSetHelper> pStateSet = new ::utl::AccessibleStateSetHelper; + + if( mpParent ) + { + pStateSet->AddState (accessibility::AccessibleStateType::ENABLED); + pStateSet->AddState (accessibility::AccessibleStateType::SENSITIVE); + pStateSet->AddState (accessibility::AccessibleStateType::SHOWING); + pStateSet->AddState (accessibility::AccessibleStateType::VISIBLE); + if ( !mbIsTransientChildrenDisabled ) + pStateSet->AddState (accessibility::AccessibleStateType::TRANSIENT); + + // SELECTABLE + pStateSet->AddState( accessibility::AccessibleStateType::SELECTABLE ); + // pStateSet->AddState( accessibility::AccessibleStateType::FOCUSABLE ); + + // SELECTED + if( mpParent->isSelected() ) + { + pStateSet->AddState( accessibility::AccessibleStateType::SELECTED ); + // pStateSet->AddState( accessibility::AccessibleStateType::FOCUSED ); + } + } + + return pStateSet; +} + +lang::Locale SAL_CALL ThumbnailViewItemAcc::getLocale() +{ + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xParent( getAccessibleParent() ); + lang::Locale aRet( "", "", "" ); + + if( xParent.is() ) + { + uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + + if( xParentContext.is() ) + aRet = xParentContext->getLocale(); + } + + return aRet; +} + +void SAL_CALL ThumbnailViewItemAcc::addAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + std::scoped_lock aGuard( maMutex ); + + if( !rxListener.is() ) + return; + + bool bFound = false; + + for (auto const& eventListener : mxEventListeners) + { + if( eventListener == rxListener ) + { + bFound = true; + break; + } + } + + if (!bFound) + mxEventListeners.push_back( rxListener ); +} + +void SAL_CALL ThumbnailViewItemAcc::removeAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + std::scoped_lock aGuard( maMutex ); + + if( rxListener.is() ) + { + std::vector< uno::Reference< accessibility::XAccessibleEventListener > >::iterator aIter = + std::find(mxEventListeners.begin(), mxEventListeners.end(), rxListener); + + if (aIter != mxEventListeners.end()) + mxEventListeners.erase( aIter ); + } +} + +sal_Bool SAL_CALL ThumbnailViewItemAcc::containsPoint( const awt::Point& aPoint ) +{ + const awt::Rectangle aRect( getBounds() ); + const Point aSize( aRect.Width, aRect.Height ); + const Point aNullPoint, aTestPoint( aPoint.X, aPoint.Y ); + + return tools::Rectangle( aNullPoint, aSize ).Contains( aTestPoint ); +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ThumbnailViewItemAcc::getAccessibleAtPoint( const awt::Point& ) +{ + uno::Reference< accessibility::XAccessible > xRet; + return xRet; +} + +awt::Rectangle SAL_CALL ThumbnailViewItemAcc::getBounds() +{ + const SolarMutexGuard aSolarGuard; + awt::Rectangle aRet; + + if( mpParent ) + { + tools::Rectangle aRect( mpParent->getDrawArea() ); + tools::Rectangle aParentRect; + + // get position of the accessible parent in screen coordinates + uno::Reference< XAccessible > xParent = getAccessibleParent(); + if ( xParent.is() ) + { + uno::Reference<XAccessibleComponent> xParentComponent(xParent->getAccessibleContext(), uno::UNO_QUERY); + if (xParentComponent.is()) + { + awt::Size aParentSize = xParentComponent->getSize(); + aParentRect = tools::Rectangle(0, 0, aParentSize.Width, aParentSize.Height); + } + } + + aRect.Intersection( aParentRect ); + + aRet.X = aRect.Left(); + aRet.Y = aRect.Top(); + aRet.Width = aRect.GetWidth(); + aRet.Height = aRect.GetHeight(); + } + + return aRet; +} + +awt::Point SAL_CALL ThumbnailViewItemAcc::getLocation() +{ + const awt::Rectangle aRect( getBounds() ); + awt::Point aRet; + + aRet.X = aRect.X; + aRet.Y = aRect.Y; + + return aRet; +} + +// get position of the accessible parent in screen coordinates +awt::Point SAL_CALL ThumbnailViewItemAcc::getLocationOnScreen() +{ + const SolarMutexGuard aSolarGuard; + awt::Point aRet; + + if (mpParent) + { + const Point aPos = mpParent->getDrawArea().TopLeft(); + const Point aScreenPos(mpParent->mrParent.GetDrawingArea()->get_accessible_location_on_screen()); + + aRet.X = aPos.X() + aScreenPos.X(); + aRet.Y = aPos.Y() + aScreenPos.X(); + } + + return aRet; +} + +awt::Size SAL_CALL ThumbnailViewItemAcc::getSize() +{ + const awt::Rectangle aRect( getBounds() ); + awt::Size aRet; + + aRet.Width = aRect.Width; + aRet.Height = aRect.Height; + + return aRet; +} + +void SAL_CALL ThumbnailViewItemAcc::grabFocus() +{ + // nothing to do +} + +sal_Int32 SAL_CALL ThumbnailViewItemAcc::getForeground( ) +{ + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowTextColor(); + return static_cast<sal_Int32>(nColor); +} + +sal_Int32 SAL_CALL ThumbnailViewItemAcc::getBackground( ) +{ + return static_cast<sal_Int32>(Application::GetSettings().GetStyleSettings().GetWindowColor()); +} + +sal_Int64 SAL_CALL ThumbnailViewItemAcc::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl(rId, this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/thumbnailviewacc.hxx b/sfx2/source/control/thumbnailviewacc.hxx new file mode 100644 index 000000000..17ff4b3fe --- /dev/null +++ b/sfx2/source/control/thumbnailviewacc.hxx @@ -0,0 +1,215 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SFX2_SOURCE_CONTROL_THUMBNAILVIEWACC_HXX +#define INCLUDED_SFX2_SOURCE_CONTROL_THUMBNAILVIEWACC_HXX + +#include <cppuhelper/implbase.hxx> +#include <comphelper/compbase.hxx> + +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> + +#include <vector> + +class ThumbnailView; +class ThumbnailViewItem; + +typedef comphelper::WeakComponentImplHelper< + css::accessibility::XAccessible, + css::accessibility::XAccessibleEventBroadcaster, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleSelection, + css::lang::XUnoTunnel > + ValueSetAccComponentBase; + +class ThumbnailViewAcc : + public ValueSetAccComponentBase +{ +public: + + ThumbnailViewAcc( ThumbnailView* pParent ); + virtual ~ThumbnailViewAcc() override; + + void FireAccessibleEvent( short nEventId, + const css::uno::Any& rOldValue, + const css::uno::Any& rNewValue ); + + bool HasAccessibleListeners() const { return( mxEventListeners.size() > 0 ); } + + static ThumbnailViewAcc* getImplementation( const css::uno::Reference< css::uno::XInterface >& rxData ) noexcept; + +public: + /** Called by the corresponding ValueSet when it gets the focus. + Stores the new focus state and broadcasts a state change event. + */ + void GetFocus(); + + /** Called by the corresponding ValueSet when it loses the focus. + Stores the new focus state and broadcasts a state change event. + */ + void LoseFocus(); + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int32 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent( ) override; + virtual sal_Int16 SAL_CALL getAccessibleRole( ) override; + virtual OUString SAL_CALL getAccessibleDescription( ) override; + virtual OUString SAL_CALL getAccessibleName( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL getAccessibleStateSet( ) override; + virtual css::lang::Locale SAL_CALL getLocale( ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XAccessibleSelection + virtual void SAL_CALL selectAccessibleChild( sal_Int32 nChildIndex ) override; + virtual sal_Bool SAL_CALL isAccessibleChildSelected( sal_Int32 nChildIndex ) override; + virtual void SAL_CALL clearAccessibleSelection( ) override; + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) override; + virtual void SAL_CALL deselectAccessibleChild( sal_Int32 nSelectedChildIndex ) override; + + // XUnoTunnel + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rId ) override; + +private: + ::std::vector< css::uno::Reference< + css::accessibility::XAccessibleEventListener > > mxEventListeners; + ThumbnailView* mpParent; + + /** Tell all listeners that the object is dying. This callback is + usually called from the WeakComponentImplHelper class. + */ + virtual void disposing(std::unique_lock<std::mutex>&) override; + + /** Return the number of items. This takes the None-Item into account. + */ + sal_uInt16 getItemCount() const; + + /** Return the item associated with the given index. The None-Item is + taken into account which, when present, is taken to be the first + (with index 0) item. + @param nIndex + Index of the item to return. The index 0 denotes the None-Item + when present. + @return + Returns NULL when the given index is out of range. + */ + ThumbnailViewItem* getItem (sal_uInt16 nIndex) const; + + /** Check whether or not the object has been disposed (or is in the + state of being disposed). If that is the case then + DisposedException is thrown to inform the (indirect) caller of the + foul deed. + + @throws css::lang::DisposedException + */ + void ThrowIfDisposed(); +}; + + +class ThumbnailViewItemAcc : public ::cppu::WeakImplHelper< css::accessibility::XAccessible, + css::accessibility::XAccessibleEventBroadcaster, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleComponent, + css::lang::XUnoTunnel > +{ +private: + + ::std::vector< css::uno::Reference< css::accessibility::XAccessibleEventListener > > + mxEventListeners; + std::mutex maMutex; + ThumbnailViewItem* mpParent; + bool mbIsTransientChildrenDisabled; + +public: + + ThumbnailViewItemAcc( ThumbnailViewItem* pParent, bool bIsTransientChildrenDisabled ); + virtual ~ThumbnailViewItemAcc() override; + + void ParentDestroyed(); + + static ThumbnailViewItemAcc* getImplementation( const css::uno::Reference< css::uno::XInterface >& rxData ) noexcept; + +public: + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int32 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent( ) override; + virtual sal_Int16 SAL_CALL getAccessibleRole( ) override; + virtual OUString SAL_CALL getAccessibleDescription( ) override; + virtual OUString SAL_CALL getAccessibleName( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL getAccessibleStateSet( ) override; + virtual css::lang::Locale SAL_CALL getLocale( ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XUnoTunnel + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rId ) override; +}; + +#endif // INCLUDED_SFX2_SOURCE_CONTROL_THUMBNAILVIEWACC_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/thumbnailviewitem.cxx b/sfx2/source/control/thumbnailviewitem.cxx new file mode 100644 index 000000000..5562acc1e --- /dev/null +++ b/sfx2/source/control/thumbnailviewitem.cxx @@ -0,0 +1,314 @@ +/* -*- 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 <sfx2/thumbnailviewitem.hxx> +#include <sfx2/thumbnailview.hxx> +#include "thumbnailviewacc.hxx" + +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/attribute/fillgraphicattribute.hxx> +#include <drawinglayer/attribute/fontattribute.hxx> +#include <drawinglayer/attribute/lineattribute.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonSelectionPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <vcl/graph.hxx> +#include <vcl/outdev.hxx> +#include <vcl/texteng.hxx> + +using namespace basegfx; +using namespace basegfx::utils; +using namespace ::com::sun::star; +using namespace drawinglayer::attribute; +using namespace drawinglayer::primitive2d; + +ThumbnailViewItem::ThumbnailViewItem(ThumbnailView& rView, sal_uInt16 nId) + : mrParent(rView) + , mnId(nId) + , mbVisible(true) + , mbBorder(true) + , mbSelected(false) + , mbHover(false) +{ +} + +ThumbnailViewItem::~ThumbnailViewItem() +{ + if( mxAcc.is() ) + { + static_cast< ThumbnailViewItemAcc* >( mxAcc.get() )->ParentDestroyed(); + } +} + +void ThumbnailViewItem::show (bool bVisible) +{ + mbVisible = bVisible; +} + +void ThumbnailViewItem::setSelection (bool state) +{ + mbSelected = state; +} + +void ThumbnailViewItem::setHighlight (bool state) +{ + mbHover = state; +} + +::tools::Rectangle ThumbnailViewItem::updateHighlight(bool bVisible, const Point& rPoint) +{ + bool bNeedsPaint = false; + + if (bVisible && getDrawArea().Contains(rPoint)) + { + if (!isHighlighted()) + bNeedsPaint = true; + setHighlight(true); + } + else + { + if (isHighlighted()) + bNeedsPaint = true; + setHighlight(false); + } + + if (bNeedsPaint) + return getDrawArea(); + + return ::tools::Rectangle(); +} + +void ThumbnailViewItem::setTitle (const OUString& rTitle) +{ + if (mrParent.renameItem(this, rTitle)) + maTitle = rTitle; +} + +uno::Reference< accessibility::XAccessible > const & ThumbnailViewItem::GetAccessible( bool bIsTransientChildrenDisabled ) +{ + if( !mxAcc.is() ) + mxAcc = new ThumbnailViewItemAcc( this, bIsTransientChildrenDisabled ); + + return mxAcc; +} + +void ThumbnailViewItem::setDrawArea (const ::tools::Rectangle &area) +{ + maDrawArea = area; +} + +void ThumbnailViewItem::calculateItemsPosition (const tools::Long nThumbnailHeight, + const tools::Long nPadding, sal_uInt32 nMaxTextLength, + const ThumbnailItemAttributes *pAttrs) +{ + drawinglayer::primitive2d::TextLayouterDevice aTextDev; + aTextDev.setFontAttribute(pAttrs->aFontAttr, + pAttrs->aFontSize.getX(), pAttrs->aFontSize.getY(), + css::lang::Locale() ); + + Size aImageSize = maPreview1.GetSizePixel(); + + // Calculate thumbnail position + const Point aPos = maDrawArea.TopCenter(); + maPrev1Pos = aPos + Point(-aImageSize.Width() / 2, nPadding + (nThumbnailHeight - aImageSize.Height()) / 2); + + // Calculate text position + maTextPos = aPos + Point(-aTextDev.getTextWidth(maTitle, 0, nMaxTextLength) / 2, nThumbnailHeight + nPadding * 2); +} + +void ThumbnailViewItem::Paint (drawinglayer::processor2d::BaseProcessor2D *pProcessor, + const ThumbnailItemAttributes *pAttrs) +{ + BColor aFillColor = pAttrs->aFillColor; + drawinglayer::primitive2d::Primitive2DContainer aSeq(4); + double fTransparence = 0.0; + + // Draw background + if( mbSelected && mbHover) + aFillColor = pAttrs->aSelectHighlightColor; + else if (mbSelected || mbHover) + { + aFillColor = pAttrs->aHighlightColor; + if (mbHover) + fTransparence = pAttrs->fHighlightTransparence; + } + + sal_uInt32 nPrimitive = 0; + aSeq[nPrimitive++] = drawinglayer::primitive2d::Primitive2DReference( + new PolyPolygonSelectionPrimitive2D( B2DPolyPolygon(::tools::Polygon(maDrawArea, THUMBNAILVIEW_ITEM_CORNER, THUMBNAILVIEW_ITEM_CORNER).getB2DPolygon()), + aFillColor, + fTransparence, + 0.0, + true)); + + // Draw thumbnail + Point aPos = maPrev1Pos; + Size aImageSize = maPreview1.GetSizePixel(); + + aSeq[nPrimitive++] = drawinglayer::primitive2d::Primitive2DReference( new FillGraphicPrimitive2D( + createTranslateB2DHomMatrix(aPos.X(),aPos.Y()), + FillGraphicAttribute(Graphic(maPreview1), + B2DRange( + B2DPoint(0,0), + B2DPoint(aImageSize.Width(),aImageSize.Height())), + false) + )); + + if (mbBorder) + { + // draw thumbnail borders + float fWidth = aImageSize.Width() - 1; + float fHeight = aImageSize.Height() - 1; + float fPosX = maPrev1Pos.getX(); + float fPosY = maPrev1Pos.getY(); + + B2DPolygon aBounds; + aBounds.append(B2DPoint(fPosX,fPosY)); + aBounds.append(B2DPoint(fPosX+fWidth,fPosY)); + aBounds.append(B2DPoint(fPosX+fWidth,fPosY+fHeight)); + aBounds.append(B2DPoint(fPosX,fPosY+fHeight)); + aBounds.setClosed(true); + + aSeq[nPrimitive++] = drawinglayer::primitive2d::Primitive2DReference(createBorderLine(aBounds)); + } + + // Draw text below thumbnail + addTextPrimitives(maTitle, pAttrs, maTextPos, aSeq); + + pProcessor->process(aSeq); +} + +void ThumbnailViewItem::addTextPrimitives (const OUString& rText, const ThumbnailItemAttributes *pAttrs, Point aPos, drawinglayer::primitive2d::Primitive2DContainer& rSeq) +{ + // adjust text drawing position according to text font + drawinglayer::primitive2d::TextLayouterDevice aTextDev; + aTextDev.setFontAttribute( + pAttrs->aFontAttr, + pAttrs->aFontSize.getX(), + pAttrs->aFontSize.getY(), + css::lang::Locale()); + + aPos.setY(aPos.getY() + aTextDev.getTextHeight()); + + sal_Int32 nMnemonicPos = -1; + OUString aOrigText(mrParent.isDrawMnemonic() ? OutputDevice::GetNonMnemonicString(rText, nMnemonicPos) : rText); + + TextEngine aTextEngine; + aTextEngine.SetFont(getVclFontFromFontAttribute(pAttrs->aFontAttr, + pAttrs->aFontSize.getX(), pAttrs->aFontSize.getY(), 0, + css::lang::Locale())); + aTextEngine.SetMaxTextWidth(maDrawArea.getWidth()); + aTextEngine.SetText(aOrigText); + + sal_Int32 nPrimitives = rSeq.size(); + sal_Int32 nFinalPrimCount = nPrimitives + aTextEngine.GetLineCount(0); + rSeq.resize(nFinalPrimCount); + + // Create the text primitives + sal_uInt16 nLineStart = 0; + OUString aText(aOrigText); + for (sal_uInt16 i=0; i < aTextEngine.GetLineCount(0); ++i) + { + sal_Int32 nLineLength = aTextEngine.GetLineLen(0, i); + double nLineWidth = aTextDev.getTextWidth (aText, nLineStart, nLineLength); + + bool bTooLong = (aPos.getY() + aTextEngine.GetCharHeight()) > maDrawArea.Bottom(); + if (bTooLong && (nLineLength + nLineStart) < aOrigText.getLength()) + { + // Add the '...' to the last line to show, even though it may require to shorten the line + double nDotsWidth = aTextDev.getTextWidth("...",0,3); + + sal_Int32 nLength = nLineLength - 1; + while ( nDotsWidth + aTextDev.getTextWidth(aText, nLineStart, nLength) > maDrawArea.getWidth() && nLength > 0) + { + --nLength; + } + + aText = OUString::Concat(aText.subView(0, nLineStart+nLength)) + "..."; + nLineLength = nLength + 3; + } + + double nLineX = maDrawArea.Left() + (maDrawArea.getWidth() - nLineWidth) / 2.0; + + basegfx::B2DHomMatrix aTextMatrix( createScaleTranslateB2DHomMatrix( + pAttrs->aFontSize.getX(), pAttrs->aFontSize.getY(), + nLineX, double( aPos.Y() ) ) ); + + // setup color + BColor aTextColor = pAttrs->aTextColor; + if(mbSelected) + { + if (mbHover) + aTextColor = pAttrs->aSelectHighlightTextColor; + else + aTextColor = pAttrs->aHighlightTextColor; + } + + rSeq[nPrimitives++] = drawinglayer::primitive2d::Primitive2DReference( + new TextSimplePortionPrimitive2D(aTextMatrix, + aText, nLineStart, nLineLength, + std::vector<double>(), + pAttrs->aFontAttr, + css::lang::Locale(), + aTextColor)); + + if (nMnemonicPos != -1 && nMnemonicPos >= nLineStart && nMnemonicPos < nLineStart + nLineLength) + { + rSeq.resize(nFinalPrimCount + 1); + + auto aCaretPositions = aTextDev.getCaretPositions(aText, nLineStart, nLineLength); + + auto lc_x1 = aCaretPositions[2*(nMnemonicPos - nLineStart)]; + auto lc_x2 = aCaretPositions[2*(nMnemonicPos - nLineStart)+1]; + auto fMnemonicWidth = std::abs(lc_x1 - lc_x2); + auto fMnemonicHeight = aTextDev.getUnderlineHeight(); + + auto fPosX = nLineX + std::min(lc_x1, lc_x2); + auto fPosY = aPos.Y() + aTextDev.getUnderlineOffset(); + + B2DPolygon aLine; + aLine.append(B2DPoint(fPosX, fPosY)); + aLine.append(B2DPoint(fPosX + fMnemonicWidth, fPosY)); + + drawinglayer::attribute::LineAttribute aLineAttribute(Color(aTextColor).getBColor(), fMnemonicHeight); + + rSeq[nPrimitives++] = drawinglayer::primitive2d::Primitive2DReference( + new PolygonStrokePrimitive2D(aLine, aLineAttribute)); + } + + nLineStart += nLineLength; + aPos.setY(aPos.getY() + aTextEngine.GetCharHeight()); + + if (bTooLong) + break; + } +} + +rtl::Reference<drawinglayer::primitive2d::PolygonHairlinePrimitive2D> +ThumbnailViewItem::createBorderLine (const basegfx::B2DPolygon& rPolygon) +{ + return new PolygonHairlinePrimitive2D(rPolygon, Color(128, 128, 128).getBColor()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/control/unoctitm.cxx b/sfx2/source/control/unoctitm.cxx new file mode 100644 index 000000000..57ef3404b --- /dev/null +++ b/sfx2/source/control/unoctitm.cxx @@ -0,0 +1,1285 @@ +/* -*- 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 <config_java.h> + +#include <tools/debug.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svl/itemset.hxx> +#include <svl/visitem.hxx> +#include <svtools/javacontext.hxx> +#include <svtools/javainteractionhandler.hxx> +#include <svl/itempool.hxx> +#include <tools/urlobj.hxx> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/status/FontHeight.hpp> +#include <com/sun/star/frame/status/ItemStatus.hpp> +#include <com/sun/star/frame/status/ItemState.hpp> +#include <com/sun/star/frame/status/Template.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/status/Visibility.hpp> +#include <comphelper/processfactory.hxx> +#include <uno/current_context.hxx> +#include <vcl/svapp.hxx> +#include <vcl/uitest/logger.hxx> +#include <boost/property_tree/json_parser.hpp> +#include <tools/json_writer.hxx> + +#include <sfx2/app.hxx> +#include <unoctitm.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/ctrlitem.hxx> +#include <sfx2/sfxuno.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/request.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/viewsh.hxx> +#include <slotserv.hxx> +#include <rtl/ustring.hxx> +#include <sfx2/lokhelper.hxx> + +#include <memory> +#include <string_view> + +#include <sal/log.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <comphelper/servicehelper.hxx> + +#include <desktop/crashreport.hxx> +#include <vcl/threadex.hxx> +#include <unotools/mediadescriptor.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; + +namespace { + +enum URLTypeId +{ + URLType_BOOL, + URLType_BYTE, + URLType_SHORT, + URLType_LONG, + URLType_HYPER, + URLType_STRING, + URLType_FLOAT, + URLType_DOUBLE, + URLType_COUNT +}; + +} + +const char* const URLTypeNames[URLType_COUNT] = +{ + "bool", + "byte", + "short", + "long", + "hyper", + "string", + "float", + "double" +}; + +static void InterceptLOKStateChangeEvent( sal_uInt16 nSID, SfxViewFrame* pViewFrame, const css::frame::FeatureStateEvent& aEvent, const SfxPoolItem* pState ); + +void SfxStatusDispatcher::ReleaseAll() +{ + css::lang::EventObject aObject; + aObject.Source = static_cast<cppu::OWeakObject*>(this); + std::unique_lock aGuard(maMutex); + maListeners.disposeAndClear( aGuard, aObject ); +} + +void SfxStatusDispatcher::sendStatusChanged(const OUString& rURL, const css::frame::FeatureStateEvent& rEvent) +{ + std::unique_lock aGuard(maMutex); + ::comphelper::OInterfaceContainerHelper4<css::frame::XStatusListener>* pContnr = maListeners.getContainer(rURL); + if (!pContnr) + return; + pContnr->forEach(aGuard, + [&rEvent](const css::uno::Reference<css::frame::XStatusListener>& xListener) + { + xListener->statusChanged(rEvent); + } + ); +} + +void SAL_CALL SfxStatusDispatcher::dispatch( const css::util::URL&, const css::uno::Sequence< css::beans::PropertyValue >& ) +{ +} + +void SAL_CALL SfxStatusDispatcher::dispatchWithNotification( + const css::util::URL&, + const css::uno::Sequence< css::beans::PropertyValue >&, + const css::uno::Reference< css::frame::XDispatchResultListener >& ) +{ +} + +SfxStatusDispatcher::SfxStatusDispatcher() +{ +} + +void SAL_CALL SfxStatusDispatcher::addStatusListener(const css::uno::Reference< css::frame::XStatusListener > & aListener, const css::util::URL& aURL) +{ + { + std::unique_lock aGuard(maMutex); + maListeners.addInterface( aGuard, aURL.Complete, aListener ); + } + if ( aURL.Complete == ".uno:LifeTime" ) + { + css::frame::FeatureStateEvent aEvent; + aEvent.FeatureURL = aURL; + aEvent.Source = static_cast<css::frame::XDispatch*>(this); + aEvent.IsEnabled = true; + aEvent.Requery = false; + aListener->statusChanged( aEvent ); + } +} + +void SAL_CALL SfxStatusDispatcher::removeStatusListener( const css::uno::Reference< css::frame::XStatusListener > & aListener, const css::util::URL& aURL ) +{ + std::unique_lock aGuard(maMutex); + maListeners.removeInterface( aGuard, aURL.Complete, aListener ); +} + + +// XUnoTunnel +sal_Int64 SAL_CALL SfxOfficeDispatch::getSomething( const css::uno::Sequence< sal_Int8 >& aIdentifier ) +{ + return comphelper::getSomethingImpl(aIdentifier, this); +} + +SfxOfficeDispatch::SfxOfficeDispatch( SfxBindings& rBindings, SfxDispatcher* pDispat, const SfxSlot* pSlot, const css::util::URL& rURL ) + : pImpl( new SfxDispatchController_Impl( this, &rBindings, pDispat, pSlot, rURL )) +{ + // pImpl is an adapter that shows a css::frame::XDispatch-Interface to the outside and uses a SfxControllerItem to monitor a state + +} + +SfxOfficeDispatch::SfxOfficeDispatch( SfxDispatcher* pDispat, const SfxSlot* pSlot, const css::util::URL& rURL ) + : pImpl( new SfxDispatchController_Impl( this, nullptr, pDispat, pSlot, rURL )) +{ + // pImpl is an adapter that shows a css::frame::XDispatch-Interface to the outside and uses a SfxControllerItem to monitor a state +} + +SfxOfficeDispatch::~SfxOfficeDispatch() +{ + if ( pImpl ) + { + // when dispatch object is released, destroy its connection to this object and destroy it + pImpl->UnBindController(); + + // Ensure that SfxDispatchController_Impl is deleted while the solar mutex is locked, since + // that derives from SfxListener. + SolarMutexGuard aGuard; + pImpl.reset(); + } +} + +const css::uno::Sequence< sal_Int8 >& SfxOfficeDispatch::getUnoTunnelId() +{ + // {38 57 CA 80 09 36 11 d4 83 FE 00 50 04 52 6B 21} + static const sal_uInt8 pGUID[16] = { 0x38, 0x57, 0xCA, 0x80, 0x09, 0x36, 0x11, 0xd4, 0x83, 0xFE, 0x00, 0x50, 0x04, 0x52, 0x6B, 0x21 }; + static css::uno::Sequence< sal_Int8 > seqID(reinterpret_cast<const sal_Int8*>(pGUID), 16) ; + return seqID ; +} + +#if HAVE_FEATURE_JAVA +// The JavaContext contains an interaction handler which is used when +// the creation of a Java Virtual Machine fails. There shall only be one +// user notification (message box) even if the same error (interaction) +// reoccurs. The effect is, that if a user selects a menu entry than they +// may get only one notification that a JRE is not selected. +// This function checks if a JavaContext is already available (typically +// created by Desktop::Main() in app.cxx), and creates new one if not. +namespace { +std::unique_ptr< css::uno::ContextLayer > EnsureJavaContext() +{ + css::uno::Reference< css::uno::XCurrentContext > xContext(css::uno::getCurrentContext()); + if (xContext.is()) + { + css::uno::Reference< css::task::XInteractionHandler > xHandler; + xContext->getValueByName(JAVA_INTERACTION_HANDLER_NAME) >>= xHandler; + if (xHandler.is()) + return nullptr; // No need to add new layer: JavaContext already present + } + return std::make_unique< css::uno::ContextLayer >(new svt::JavaContext(xContext)); +} +} +#endif + +void SAL_CALL SfxOfficeDispatch::dispatch( const css::util::URL& aURL, const css::uno::Sequence< css::beans::PropertyValue >& aArgs ) +{ + // ControllerItem is the Impl class + if ( pImpl ) + { +#if HAVE_FEATURE_JAVA + std::unique_ptr< css::uno::ContextLayer > layer(EnsureJavaContext()); +#endif + utl::MediaDescriptor aDescriptor(aArgs); + bool bOnMainThread = aDescriptor.getUnpackedValueOrDefault("OnMainThread", false); + if (bOnMainThread) + { + // Make sure that we own the solar mutex, otherwise later + // vcl::SolarThreadExecutor::execute() will release the solar mutex, even if it's owned by + // an other thread, leading to an std::abort() at the end. + SolarMutexGuard aGuard; + vcl::solarthread::syncExecute([this, &aURL, &aArgs]() { + pImpl->dispatch(aURL, aArgs, + css::uno::Reference<css::frame::XDispatchResultListener>()); + }); + } + else + { + pImpl->dispatch(aURL, aArgs, + css::uno::Reference<css::frame::XDispatchResultListener>()); + } + } +} + +void SAL_CALL SfxOfficeDispatch::dispatchWithNotification( const css::util::URL& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& aArgs, + const css::uno::Reference< css::frame::XDispatchResultListener >& rListener ) +{ + // ControllerItem is the Impl class + if ( pImpl ) + { +#if HAVE_FEATURE_JAVA + std::unique_ptr< css::uno::ContextLayer > layer(EnsureJavaContext()); +#endif + pImpl->dispatch( aURL, aArgs, rListener ); + } +} + +void SAL_CALL SfxOfficeDispatch::addStatusListener(const css::uno::Reference< css::frame::XStatusListener > & aListener, const css::util::URL& aURL) +{ + { + std::unique_lock aGuard(maMutex); + maListeners.addInterface( aGuard, aURL.Complete, aListener ); + } + if ( pImpl ) + { + // ControllerItem is the Impl class + pImpl->addStatusListener( aListener, aURL ); + } +} + +SfxDispatcher* SfxOfficeDispatch::GetDispatcher_Impl() +{ + return pImpl->GetDispatcher(); +} + +sal_uInt16 SfxOfficeDispatch::GetId() const +{ + return pImpl ? pImpl->GetId() : 0; +} + +void SfxOfficeDispatch::SetFrame(const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + if ( pImpl ) + pImpl->SetFrame( xFrame ); +} + +void SfxOfficeDispatch::SetMasterUnoCommand( bool bSet ) +{ + if ( pImpl ) + pImpl->setMasterSlaveCommand( bSet ); +} + +// Determine if URL contains a master/slave command which must be handled a little bit different +bool SfxOfficeDispatch::IsMasterUnoCommand( const css::util::URL& aURL ) +{ + return aURL.Protocol == ".uno:" && ( aURL.Path.indexOf( '.' ) > 0 ); +} + +OUString SfxOfficeDispatch::GetMasterUnoCommand( const css::util::URL& aURL ) +{ + OUString aMasterCommand; + if ( IsMasterUnoCommand( aURL )) + { + sal_Int32 nIndex = aURL.Path.indexOf( '.' ); + if ( nIndex > 0 ) + aMasterCommand = aURL.Path.copy( 0, nIndex ); + } + + return aMasterCommand; +} + +SfxDispatchController_Impl::SfxDispatchController_Impl( + SfxOfficeDispatch* pDisp, + SfxBindings* pBind, + SfxDispatcher* pDispat, + const SfxSlot* pSlot, + const css::util::URL& rURL ) + : aDispatchURL( rURL ) + , pDispatcher( pDispat ) + , pBindings( pBind ) + , pLastState( nullptr ) + , pDispatch( pDisp ) + , bMasterSlave( false ) + , bVisible( true ) +{ + if ( aDispatchURL.Protocol == "slot:" && pSlot->pUnoName ) + { + aDispatchURL.Complete = ".uno:" + OUString::createFromAscii(pSlot->pUnoName); + Reference< XURLTransformer > xTrans( URLTransformer::create( ::comphelper::getProcessComponentContext() ) ); + xTrans->parseStrict( aDispatchURL ); + } + + sal_uInt16 nSlot = pSlot->GetSlotId(); + SetId( nSlot ); + if ( pBindings ) + { + // Bind immediately to enable the cache to recycle dispatches when asked for the same command + // a command in "slot" or in ".uno" notation must be treated as identical commands! + pBindings->ENTERREGISTRATIONS(); + BindInternal_Impl( nSlot, pBindings ); + pBindings->LEAVEREGISTRATIONS(); + } + assert(pDispatcher); + assert(SfxApplication::Get()->GetAppDispatcher_Impl() == pDispatcher + || pDispatcher->GetFrame() != nullptr); + if (pDispatcher->GetFrame()) + { + StartListening(*pDispatcher->GetFrame()); + } + else + { + StartListening(*SfxApplication::Get()); + } +} + +void SfxDispatchController_Impl::Notify(SfxBroadcaster& rBC, SfxHint const& rHint) +{ + if (rHint.GetId() == SfxHintId::Dying) + { // both pBindings and pDispatcher are dead if SfxViewFrame is dead + pBindings = nullptr; + pDispatcher = nullptr; + EndListening(rBC); + } +} + +SfxDispatchController_Impl::~SfxDispatchController_Impl() +{ + if ( pLastState && !IsInvalidItem( pLastState ) ) + delete pLastState; + + if ( pDispatch ) + { + // disconnect + pDispatch->pImpl = nullptr; + + // force all listeners to release the dispatch object + pDispatch->ReleaseAll(); + } +} + +void SfxDispatchController_Impl::SetFrame(const css::uno::Reference< css::frame::XFrame >& _xFrame) +{ + xFrame = _xFrame; +} + +void SfxDispatchController_Impl::setMasterSlaveCommand( bool bSet ) +{ + bMasterSlave = bSet; +} + +void SfxDispatchController_Impl::UnBindController() +{ + pDispatch = nullptr; + if ( IsBound() ) + { + GetBindings().ENTERREGISTRATIONS(); + SfxControllerItem::UnBind(); + GetBindings().LEAVEREGISTRATIONS(); + } +} + +void SfxDispatchController_Impl::addParametersToArgs( const css::util::URL& aURL, css::uno::Sequence< css::beans::PropertyValue >& rArgs ) +{ + // Extract the parameter from the URL and put them into the property value sequence + sal_Int32 nQueryIndex = aURL.Complete.indexOf( '?' ); + if ( nQueryIndex <= 0 ) + return; + + OUString aParamString( aURL.Complete.copy( nQueryIndex+1 )); + sal_Int32 nIndex = 0; + do + { + OUString aToken = aParamString.getToken( 0, '&', nIndex ); + + sal_Int32 nParmIndex = 0; + OUString aParamType; + OUString aParamName = aToken.getToken( 0, '=', nParmIndex ); + OUString aValue = aToken.getToken( 0, '=', nParmIndex ); + + if ( !aParamName.isEmpty() ) + { + nParmIndex = 0; + aToken = aParamName; + aParamName = aToken.getToken( 0, ':', nParmIndex ); + aParamType = aToken.getToken( 0, ':', nParmIndex ); + } + + sal_Int32 nLen = rArgs.getLength(); + rArgs.realloc( nLen+1 ); + auto pArgs = rArgs.getArray(); + pArgs[nLen].Name = aParamName; + + if ( aParamType.isEmpty() ) + { + // Default: LONG + pArgs[nLen].Value <<= aValue.toInt32(); + } + else if ( aParamType.equalsAsciiL( URLTypeNames[URLType_BOOL], 4 )) + { + // sal_Bool support + pArgs[nLen].Value <<= aValue.toBoolean(); + } + else if ( aParamType.equalsAsciiL( URLTypeNames[URLType_BYTE], 4 )) + { + // sal_uInt8 support + pArgs[nLen].Value <<= sal_Int8( aValue.toInt32() ); + } + else if ( aParamType.equalsAsciiL( URLTypeNames[URLType_LONG], 4 )) + { + // LONG support + pArgs[nLen].Value <<= aValue.toInt32(); + } + else if ( aParamType.equalsAsciiL( URLTypeNames[URLType_SHORT], 5 )) + { + // SHORT support + pArgs[nLen].Value <<= sal_Int16( aValue.toInt32() ); + } + else if ( aParamType.equalsAsciiL( URLTypeNames[URLType_HYPER], 5 )) + { + // HYPER support + pArgs[nLen].Value <<= aValue.toInt64(); + } + else if ( aParamType.equalsAsciiL( URLTypeNames[URLType_FLOAT], 5 )) + { + // FLOAT support + pArgs[nLen].Value <<= aValue.toFloat(); + } + else if ( aParamType.equalsAsciiL( URLTypeNames[URLType_STRING], 6 )) + { + // STRING support + pArgs[nLen].Value <<= INetURLObject::decode( aValue, INetURLObject::DecodeMechanism::WithCharset ); + } + else if ( aParamType.equalsAsciiL( URLTypeNames[URLType_DOUBLE], 6)) + { + // DOUBLE support + pArgs[nLen].Value <<= aValue.toDouble(); + } + } + while ( nIndex >= 0 ); +} + +MapUnit SfxDispatchController_Impl::GetCoreMetric( SfxItemPool const & rPool, sal_uInt16 nSlotId ) +{ + sal_uInt16 nWhich = rPool.GetWhich( nSlotId ); + return rPool.GetMetric( nWhich ); +} + +OUString SfxDispatchController_Impl::getSlaveCommand( const css::util::URL& rURL ) +{ + OUString aSlaveCommand; + sal_Int32 nIndex = rURL.Path.indexOf( '.' ); + if (( nIndex > 0 ) && ( nIndex < rURL.Path.getLength() )) + aSlaveCommand = rURL.Path.copy( nIndex+1 ); + return aSlaveCommand; +} + +namespace { + +void collectUIInformation(const util::URL& rURL, const css::uno::Sequence< css::beans::PropertyValue >& rArgs) +{ + static const char* pFile = std::getenv("LO_COLLECT_UIINFO"); + if (!pFile) + return; + + UITestLogger::getInstance().logCommand( + OUStringConcatenation("Send UNO Command (\"" + rURL.Complete + "\") "), rArgs); +} + +} + +void SfxDispatchController_Impl::dispatch( const css::util::URL& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& aArgs, + const css::uno::Reference< css::frame::XDispatchResultListener >& rListener ) +{ + if ( aURL.Protocol == ".uno:") + { + CrashReporter::logUnoCommand(aURL.Path); + } + collectUIInformation(aURL, aArgs); + + SolarMutexGuard aGuard; + + if (comphelper::LibreOfficeKit::isActive() && + SfxViewShell::Current()->isBlockedCommand(aURL.Complete)) + { + tools::JsonWriter aTree; + aTree.put("code", ""); + aTree.put("kind", "BlockedCommand"); + aTree.put("cmd", aURL.Complete); + aTree.put("message", "Blocked feature"); + aTree.put("viewID", SfxViewShell::Current()->GetViewShellId().get()); + + SfxViewShell::Current()->libreOfficeKitViewCallback(LOK_COMMAND_BLOCKED, aTree.extractData()); + return; + } + + if ( + !(pDispatch && + ( + (aURL.Protocol == ".uno:" && aURL.Path == aDispatchURL.Path) || + (aURL.Protocol == "slot:" && aURL.Path.toInt32() == GetId()) + )) + ) + return; + + if ( !pDispatcher && pBindings ) + pDispatcher = GetBindings().GetDispatcher_Impl(); + + css::uno::Sequence< css::beans::PropertyValue > lNewArgs; + sal_Int32 nCount = aArgs.getLength(); + + // Support for URL based arguments + INetURLObject aURLObj( aURL.Complete ); + if ( aURLObj.HasParam() ) + addParametersToArgs( aURL, lNewArgs ); + + // Try to find call mode and frame name inside given arguments... + SfxCallMode nCall = SfxCallMode::RECORD; + sal_Int32 nMarkArg = -1; + + // Filter arguments which shouldn't be part of the sequence property value + sal_uInt16 nModifier(0); + std::vector< css::beans::PropertyValue > aAddArgs; + for( sal_Int32 n=0; n<nCount; n++ ) + { + const css::beans::PropertyValue& rProp = aArgs[n]; + if( rProp.Name == "SynchronMode" ) + { + bool bTemp; + if( rProp.Value >>= bTemp ) + nCall = bTemp ? SfxCallMode::SYNCHRON : SfxCallMode::ASYNCHRON; + } + else if( rProp.Name == "Bookmark" ) + { + nMarkArg = n; + aAddArgs.push_back( aArgs[n] ); + } + else if( rProp.Name == "KeyModifier" ) + rProp.Value >>= nModifier; + else + aAddArgs.push_back( aArgs[n] ); + } + + // Add needed arguments to sequence property value + sal_uInt32 nAddArgs = aAddArgs.size(); + if ( nAddArgs > 0 ) + { + sal_uInt32 nIndex( lNewArgs.getLength() ); + + lNewArgs.realloc( nIndex + nAddArgs ); + std::copy(aAddArgs.begin(), aAddArgs.end(), std::next(lNewArgs.getArray(), nIndex)); + } + + // Overwrite possible detected synchron argument, if real listener exists (currently no other way) + if ( rListener.is() ) + nCall = SfxCallMode::SYNCHRON; + + if( GetId() == SID_JUMPTOMARK && nMarkArg == - 1 ) + { + // we offer dispatches for SID_JUMPTOMARK if the URL points to a bookmark inside the document + // so we must retrieve this as an argument from the parsed URL + lNewArgs.realloc( lNewArgs.getLength()+1 ); + auto& el = lNewArgs.getArray()[lNewArgs.getLength()-1]; + el.Name = "Bookmark"; + el.Value <<= aURL.Mark; + } + + css::uno::Reference< css::frame::XFrame > xFrameRef(xFrame.get(), css::uno::UNO_QUERY); + if (! xFrameRef.is() && pDispatcher) + { + SfxViewFrame* pViewFrame = pDispatcher->GetFrame(); + if (pViewFrame) + xFrameRef = pViewFrame->GetFrame().GetFrameInterface(); + } + + bool bSuccess = false; + const SfxPoolItem* pItem = nullptr; + MapUnit eMapUnit( MapUnit::Map100thMM ); + + // Extra scope so that aInternalSet is destroyed before + // rListener->dispatchFinished potentially calls + // framework::Desktop::terminate -> SfxApplication::Deinitialize -> + // ~CntItemPool: + if (pDispatcher) + { + SfxAllItemSet aInternalSet( SfxGetpApp()->GetPool() ); + if (xFrameRef.is()) // an empty set is no problem ... but an empty frame reference can be a problem ! + aInternalSet.Put( SfxUnoFrameItem( SID_FILLFRAME, xFrameRef ) ); + + SfxShell* pShell( nullptr ); + // #i102619# Retrieve metric from shell before execution - the shell could be destroyed after execution + if ( pDispatcher->GetBindings() ) + { + if ( !pDispatcher->IsLocked() ) + { + const SfxSlot *pSlot = nullptr; + if ( pDispatcher->GetShellAndSlot_Impl( GetId(), &pShell, &pSlot, false, false ) ) + { + if ( bMasterSlave ) + { + // Extract slave command and add argument to the args list. Master slot MUST + // have an argument that has the same name as the master slot and type is SfxStringItem. + sal_Int32 nIndex = lNewArgs.getLength(); + lNewArgs.realloc( nIndex+1 ); + auto plNewArgs = lNewArgs.getArray(); + plNewArgs[nIndex].Name = OUString::createFromAscii( pSlot->pUnoName ); + plNewArgs[nIndex].Value <<= SfxDispatchController_Impl::getSlaveCommand( aDispatchURL ); + } + + eMapUnit = GetCoreMetric( pShell->GetPool(), GetId() ); + std::optional<SfxAllItemSet> xSet(pShell->GetPool()); + TransformParameters(GetId(), lNewArgs, *xSet, pSlot); + if (xSet->Count()) + { + // execute with arguments - call directly + pItem = pDispatcher->Execute(GetId(), nCall, &*xSet, &aInternalSet, nModifier); + if ( pItem != nullptr ) + { + if (const SfxBoolItem* pBoolItem = dynamic_cast<const SfxBoolItem*>(pItem)) + bSuccess = pBoolItem->GetValue(); + else if ( !pItem->IsVoidItem() ) + bSuccess = true; // all other types are true + } + // else bSuccess = false look to line 664 it is false + } + else + { + // Be sure to delete this before we send a dispatch + // request, which will destroy the current shell. + xSet.reset(); + + // execute using bindings, enables support for toggle/enum etc. + SfxRequest aReq( GetId(), nCall, pShell->GetPool() ); + aReq.SetModifier( nModifier ); + aReq.SetInternalArgs_Impl(aInternalSet); + pDispatcher->GetBindings()->Execute_Impl( aReq, pSlot, pShell ); + pItem = aReq.GetReturnValue(); + bSuccess = aReq.IsDone() || pItem != nullptr; + } + } + else + SAL_INFO("sfx.control", "MacroPlayer: Unknown slot dispatched!"); + } + } + else + { + eMapUnit = GetCoreMetric( SfxGetpApp()->GetPool(), GetId() ); + // AppDispatcher + SfxAllItemSet aSet( SfxGetpApp()->GetPool() ); + TransformParameters( GetId(), lNewArgs, aSet ); + + if ( aSet.Count() ) + pItem = pDispatcher->Execute(GetId(), nCall, &aSet, &aInternalSet, nModifier); + else + // SfxRequests take empty sets as argument sets, GetArgs() returning non-zero! + pItem = pDispatcher->Execute(GetId(), nCall, nullptr, &aInternalSet, nModifier); + + // no bindings, no invalidate ( usually done in SfxDispatcher::Call_Impl()! ) + if (SfxApplication* pApp = SfxApplication::Get()) + { + SfxDispatcher* pAppDispat = pApp->GetAppDispatcher_Impl(); + if ( pAppDispat ) + { + const SfxPoolItem* pState=nullptr; + SfxItemState eState = pDispatcher->QueryState( GetId(), pState ); + StateChangedAtToolBoxControl( GetId(), eState, pState ); + } + } + + bSuccess = (pItem != nullptr); + } + } + + if ( !rListener.is() ) + return; + + css::frame::DispatchResultEvent aEvent; + if ( bSuccess ) + aEvent.State = css::frame::DispatchResultState::SUCCESS; + else + aEvent.State = css::frame::DispatchResultState::FAILURE; + + aEvent.Source = static_cast<css::frame::XDispatch*>(pDispatch); + if ( bSuccess && pItem && !pItem->IsVoidItem() ) + { + sal_uInt16 nSubId( 0 ); + if ( eMapUnit == MapUnit::MapTwip ) + nSubId |= CONVERT_TWIPS; + pItem->QueryValue( aEvent.Result, static_cast<sal_uInt8>(nSubId) ); + } + + rListener->dispatchFinished( aEvent ); +} + +SfxDispatcher* SfxDispatchController_Impl::GetDispatcher() +{ + if ( !pDispatcher && pBindings ) + pDispatcher = GetBindings().GetDispatcher_Impl(); + return pDispatcher; +} + +void SfxDispatchController_Impl::addStatusListener(const css::uno::Reference< css::frame::XStatusListener > & aListener, const css::util::URL& aURL) +{ + SolarMutexGuard aGuard; + if ( !pDispatch ) + return; + + // Use alternative QueryState call to have a valid UNO representation of the state. + css::uno::Any aState; + if ( !pDispatcher && pBindings ) + pDispatcher = GetBindings().GetDispatcher_Impl(); + SfxItemState eState = pDispatcher ? pDispatcher->QueryState( GetId(), aState ) : SfxItemState::DONTCARE; + + if ( eState == SfxItemState::DONTCARE ) + { + // Use special uno struct to transport don't care state + css::frame::status::ItemStatus aItemStatus; + aItemStatus.State = css::frame::status::ItemState::DONT_CARE; + aState <<= aItemStatus; + } + + css::frame::FeatureStateEvent aEvent; + aEvent.FeatureURL = aURL; + aEvent.Source = static_cast<css::frame::XDispatch*>(pDispatch); + aEvent.Requery = false; + if ( bVisible ) + { + aEvent.IsEnabled = eState != SfxItemState::DISABLED; + aEvent.State = aState; + } + else + { + css::frame::status::Visibility aVisibilityStatus; + aVisibilityStatus.bVisible = false; + + // MBA: we might decide to *not* disable "invisible" slots, but this would be + // a change that needs to adjust at least the testtool + aEvent.IsEnabled = false; + aEvent.State <<= aVisibilityStatus; + } + + aListener->statusChanged( aEvent ); +} + +void SfxDispatchController_Impl::sendStatusChanged(const OUString& rURL, const css::frame::FeatureStateEvent& rEvent) +{ + pDispatch->sendStatusChanged(rURL, rEvent); +} + +void SfxDispatchController_Impl::StateChanged( sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState, SfxSlotServer const * pSlotServ ) +{ + if ( !pDispatch ) + return; + + // Bindings instance notifies controller about a state change, listeners must be notified also + // Don't cache visibility state changes as they are volatile. We need our real state to send it + // to our controllers after visibility is set to true. + bool bNotify = true; + if ( pState && !IsInvalidItem( pState ) ) + { + if ( auto pVisibilityItem = dynamic_cast< const SfxVisibilityItem *>( pState ) ) + bVisible = pVisibilityItem->GetValue(); + else + { + if (pLastState && !IsInvalidItem(pLastState)) + { + bNotify = typeid(*pState) != typeid(*pLastState) || *pState != *pLastState; + delete pLastState; + } + pLastState = !IsInvalidItem(pState) ? pState->Clone() : pState; + bVisible = true; + } + } + else + { + if ( pLastState && !IsInvalidItem( pLastState ) ) + delete pLastState; + pLastState = pState; + } + + if (!bNotify) + return; + + css::uno::Any aState; + if ( ( eState >= SfxItemState::DEFAULT ) && pState && !IsInvalidItem( pState ) && !pState->IsVoidItem() ) + { + // Retrieve metric from pool to have correct sub ID when calling QueryValue + sal_uInt16 nSubId( 0 ); + MapUnit eMapUnit( MapUnit::Map100thMM ); + + // retrieve the core metric + // it's enough to check the objectshell, the only shell that does not use the pool of the document + // is SfxViewFrame, but it hasn't any metric parameters + // TODO/LATER: what about the FormShell? Does it use any metric data?! Perhaps it should use the Pool of the document! + if ( pSlotServ && pDispatcher ) + { + SfxShell* pShell = pDispatcher->GetShell( pSlotServ->GetShellLevel() ); + DBG_ASSERT( pShell, "Can't get core metric without shell!" ); + if ( pShell ) + eMapUnit = GetCoreMetric( pShell->GetPool(), nSID ); + } + + if ( eMapUnit == MapUnit::MapTwip ) + nSubId |= CONVERT_TWIPS; + + pState->QueryValue( aState, static_cast<sal_uInt8>(nSubId) ); + } + else if ( eState == SfxItemState::DONTCARE ) + { + // Use special uno struct to transport don't care state + css::frame::status::ItemStatus aItemStatus; + aItemStatus.State = css::frame::status::ItemState::DONT_CARE; + aState <<= aItemStatus; + } + + css::frame::FeatureStateEvent aEvent; + aEvent.FeatureURL = aDispatchURL; + aEvent.Source = static_cast<css::frame::XDispatch*>(pDispatch); + aEvent.IsEnabled = eState != SfxItemState::DISABLED; + aEvent.Requery = false; + aEvent.State = aState; + + if (pDispatcher && pDispatcher->GetFrame()) + { + InterceptLOKStateChangeEvent(nSID, pDispatcher->GetFrame(), aEvent, pState); + } + + const std::vector<OUString> aContainedTypes = pDispatch->getContainedTypes(); + for (const OUString& rName: aContainedTypes) + { + if (rName == aDispatchURL.Main || rName == aDispatchURL.Complete) + sendStatusChanged(rName, aEvent); + } +} + +void SfxDispatchController_Impl::StateChangedAtToolBoxControl( sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState ) +{ + StateChanged( nSID, eState, pState, nullptr ); +} + +static void InterceptLOKStateChangeEvent(sal_uInt16 nSID, SfxViewFrame* pViewFrame, const css::frame::FeatureStateEvent& aEvent, const SfxPoolItem* pState) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + OUStringBuffer aBuffer; + aBuffer.append(aEvent.FeatureURL.Complete); + aBuffer.append(u'='); + + if (aEvent.FeatureURL.Path == "Bold" || + aEvent.FeatureURL.Path == "CenterPara" || + aEvent.FeatureURL.Path == "CharBackgroundExt" || + aEvent.FeatureURL.Path == "ControlCodes" || + aEvent.FeatureURL.Path == "DefaultBullet" || + aEvent.FeatureURL.Path == "DefaultNumbering" || + aEvent.FeatureURL.Path == "Italic" || + aEvent.FeatureURL.Path == "JustifyPara" || + aEvent.FeatureURL.Path == "LeftPara" || + aEvent.FeatureURL.Path == "OutlineFont" || + aEvent.FeatureURL.Path == "RightPara" || + aEvent.FeatureURL.Path == "Shadowed" || + aEvent.FeatureURL.Path == "SpellOnline" || + aEvent.FeatureURL.Path == "OnlineAutoFormat" || + aEvent.FeatureURL.Path == "SubScript" || + aEvent.FeatureURL.Path == "SuperScript" || + aEvent.FeatureURL.Path == "Strikeout" || + aEvent.FeatureURL.Path == "Underline" || + aEvent.FeatureURL.Path == "ModifiedStatus" || + aEvent.FeatureURL.Path == "TrackChanges" || + aEvent.FeatureURL.Path == "ShowTrackedChanges" || + aEvent.FeatureURL.Path == "NextTrackedChange" || + aEvent.FeatureURL.Path == "PreviousTrackedChange" || + aEvent.FeatureURL.Path == "AlignLeft" || + aEvent.FeatureURL.Path == "AlignHorizontalCenter" || + aEvent.FeatureURL.Path == "AlignRight" || + aEvent.FeatureURL.Path == "DocumentRepair" || + aEvent.FeatureURL.Path == "ObjectAlignLeft" || + aEvent.FeatureURL.Path == "ObjectAlignRight" || + aEvent.FeatureURL.Path == "AlignCenter" || + aEvent.FeatureURL.Path == "AlignUp" || + aEvent.FeatureURL.Path == "AlignMiddle" || + aEvent.FeatureURL.Path == "AlignDown" || + aEvent.FeatureURL.Path == "TraceChangeMode" || + aEvent.FeatureURL.Path == "FormatPaintbrush" || + aEvent.FeatureURL.Path == "FreezePanes" || + aEvent.FeatureURL.Path == "Sidebar" || + aEvent.FeatureURL.Path == "SheetRightToLeft" || + aEvent.FeatureURL.Path == "SpacePara1" || + aEvent.FeatureURL.Path == "SpacePara15" || + aEvent.FeatureURL.Path == "SpacePara2") + { + bool bTemp = false; + aEvent.State >>= bTemp; + aBuffer.append(bTemp); + } + else if (aEvent.FeatureURL.Path == "CharFontName") + { + css::awt::FontDescriptor aFontDesc; + aEvent.State >>= aFontDesc; + aBuffer.append(aFontDesc.Name); + } + else if (aEvent.FeatureURL.Path == "FontHeight") + { + css::frame::status::FontHeight aFontHeight; + aEvent.State >>= aFontHeight; + aBuffer.append(aFontHeight.Height); + } + else if (aEvent.FeatureURL.Path == "StyleApply") + { + css::frame::status::Template aTemplate; + aEvent.State >>= aTemplate; + aBuffer.append(aTemplate.StyleName); + } + else if (aEvent.FeatureURL.Path == "BackColor" || + aEvent.FeatureURL.Path == "BackgroundColor" || + aEvent.FeatureURL.Path == "CharBackColor" || + aEvent.FeatureURL.Path == "Color" || + aEvent.FeatureURL.Path == "FontColor" || + aEvent.FeatureURL.Path == "FrameLineColor" || + aEvent.FeatureURL.Path == "GlowColor") + { + sal_Int32 nColor = -1; + aEvent.State >>= nColor; + aBuffer.append(nColor); + } + else if (aEvent.FeatureURL.Path == "Undo" || + aEvent.FeatureURL.Path == "Redo") + { + const SfxUInt32Item* pUndoConflict = dynamic_cast< const SfxUInt32Item * >( pState ); + if ( pUndoConflict && pUndoConflict->GetValue() > 0 ) + { + aBuffer.append("disabled"); + } + else + { + aBuffer.append(aEvent.IsEnabled ? std::u16string_view(u"enabled") : std::u16string_view(u"disabled")); + } + } + else if (aEvent.FeatureURL.Path == "Cut" || + aEvent.FeatureURL.Path == "Copy" || + aEvent.FeatureURL.Path == "Paste" || + aEvent.FeatureURL.Path == "SelectAll" || + aEvent.FeatureURL.Path == "InsertAnnotation" || + aEvent.FeatureURL.Path == "DeleteAnnotation" || + aEvent.FeatureURL.Path == "ResolveAnnotation" || + aEvent.FeatureURL.Path == "ResolveAnnotationThread" || + aEvent.FeatureURL.Path == "InsertRowsBefore" || + aEvent.FeatureURL.Path == "InsertRowsAfter" || + aEvent.FeatureURL.Path == "InsertColumnsBefore" || + aEvent.FeatureURL.Path == "InsertColumnsAfter" || + aEvent.FeatureURL.Path == "MergeCells" || + aEvent.FeatureURL.Path == "InsertObjectChart" || + aEvent.FeatureURL.Path == "InsertSection" || + aEvent.FeatureURL.Path == "InsertAnnotation" || + aEvent.FeatureURL.Path == "InsertPagebreak" || + aEvent.FeatureURL.Path == "InsertColumnBreak" || + aEvent.FeatureURL.Path == "HyperlinkDialog" || + aEvent.FeatureURL.Path == "InsertSymbol" || + aEvent.FeatureURL.Path == "InsertPage" || + aEvent.FeatureURL.Path == "DeletePage" || + aEvent.FeatureURL.Path == "DuplicatePage" || + aEvent.FeatureURL.Path == "DeleteRows" || + aEvent.FeatureURL.Path == "DeleteColumns" || + aEvent.FeatureURL.Path == "DeleteTable" || + aEvent.FeatureURL.Path == "SelectTable" || + aEvent.FeatureURL.Path == "EntireRow" || + aEvent.FeatureURL.Path == "EntireColumn" || + aEvent.FeatureURL.Path == "EntireCell" || + aEvent.FeatureURL.Path == "SortAscending" || + aEvent.FeatureURL.Path == "SortDescending" || + aEvent.FeatureURL.Path == "AcceptAllTrackedChanges" || + aEvent.FeatureURL.Path == "RejectAllTrackedChanges" || + aEvent.FeatureURL.Path == "AcceptTrackedChange" || + aEvent.FeatureURL.Path == "RejectTrackedChange" || + aEvent.FeatureURL.Path == "NextTrackedChange" || + aEvent.FeatureURL.Path == "PreviousTrackedChange" || + aEvent.FeatureURL.Path == "FormatGroup" || + aEvent.FeatureURL.Path == "ObjectBackOne" || + aEvent.FeatureURL.Path == "SendToBack" || + aEvent.FeatureURL.Path == "ObjectForwardOne" || + aEvent.FeatureURL.Path == "BringToFront" || + aEvent.FeatureURL.Path == "WrapRight" || + aEvent.FeatureURL.Path == "WrapThrough" || + aEvent.FeatureURL.Path == "WrapLeft" || + aEvent.FeatureURL.Path == "WrapIdeal" || + aEvent.FeatureURL.Path == "WrapOn" || + aEvent.FeatureURL.Path == "WrapOff" || + aEvent.FeatureURL.Path == "UpdateCurIndex" || + aEvent.FeatureURL.Path == "InsertCaptionDialog" || + aEvent.FeatureURL.Path == "MergeCells" || + aEvent.FeatureURL.Path == "SplitTable" || + aEvent.FeatureURL.Path == "SplitCell" || + aEvent.FeatureURL.Path == "DeleteNote" || + aEvent.FeatureURL.Path == "AcceptChanges" || + aEvent.FeatureURL.Path == "SetDefault" || + aEvent.FeatureURL.Path == "ParaLeftToRight" || + aEvent.FeatureURL.Path == "ParaRightToLeft" || + aEvent.FeatureURL.Path == "ParaspaceIncrease" || + aEvent.FeatureURL.Path == "ParaspaceDecrease" || + aEvent.FeatureURL.Path == "TableDialog" || + aEvent.FeatureURL.Path == "FormatCellDialog" || + aEvent.FeatureURL.Path == "FontDialog" || + aEvent.FeatureURL.Path == "ParagraphDialog" || + aEvent.FeatureURL.Path == "OutlineBullet" || + aEvent.FeatureURL.Path == "InsertIndexesEntry" || + aEvent.FeatureURL.Path == "TransformDialog" || + aEvent.FeatureURL.Path == "EditRegion" || + aEvent.FeatureURL.Path == "ThesaurusDialog" || + aEvent.FeatureURL.Path == "OutlineRight" || + aEvent.FeatureURL.Path == "OutlineLeft" || + aEvent.FeatureURL.Path == "OutlineDown" || + aEvent.FeatureURL.Path == "OutlineUp" || + aEvent.FeatureURL.Path == "FormatArea" || + aEvent.FeatureURL.Path == "FormatLine" || + aEvent.FeatureURL.Path == "FormatColumns" || + aEvent.FeatureURL.Path == "Watermark" || + aEvent.FeatureURL.Path == "InsertBreak" || + aEvent.FeatureURL.Path == "InsertEndnote" || + aEvent.FeatureURL.Path == "InsertFootnote" || + aEvent.FeatureURL.Path == "InsertReferenceField" || + aEvent.FeatureURL.Path == "InsertBookmark" || + aEvent.FeatureURL.Path == "InsertAuthoritiesEntry" || + aEvent.FeatureURL.Path == "InsertMultiIndex" || + aEvent.FeatureURL.Path == "InsertField" || + aEvent.FeatureURL.Path == "InsertPageNumberField" || + aEvent.FeatureURL.Path == "InsertPageCountField" || + aEvent.FeatureURL.Path == "InsertDateField" || + aEvent.FeatureURL.Path == "InsertTitleField" || + aEvent.FeatureURL.Path == "InsertFieldCtrl" || + aEvent.FeatureURL.Path == "CharmapControl" || + aEvent.FeatureURL.Path == "EnterGroup" || + aEvent.FeatureURL.Path == "LeaveGroup" || + aEvent.FeatureURL.Path == "Combine" || + aEvent.FeatureURL.Path == "Merge" || + aEvent.FeatureURL.Path == "Dismantle" || + aEvent.FeatureURL.Path == "Substract" || + aEvent.FeatureURL.Path == "DistributeSelection" || + aEvent.FeatureURL.Path == "Intersect" || + aEvent.FeatureURL.Path == "ResetAttributes" || + aEvent.FeatureURL.Path == "IncrementIndent" || + aEvent.FeatureURL.Path == "DecrementIndent" || + aEvent.FeatureURL.Path == "EditHeaderAndFooter" || + aEvent.FeatureURL.Path == "InsertSparkline" || + aEvent.FeatureURL.Path == "DeleteSparkline" || + aEvent.FeatureURL.Path == "DeleteSparklineGroup" || + aEvent.FeatureURL.Path == "EditSparklineGroup" || + aEvent.FeatureURL.Path == "EditSparkline" || + aEvent.FeatureURL.Path == "GroupSparklines" || + aEvent.FeatureURL.Path == "UngroupSparklines" || + aEvent.FeatureURL.Path == "FormatSparklineMenu" || + aEvent.FeatureURL.Path == "NumberFormatDecDecimals" || + aEvent.FeatureURL.Path == "NumberFormatIncDecimals") + { + aBuffer.append(aEvent.IsEnabled ? std::u16string_view(u"enabled") : std::u16string_view(u"disabled")); + } + else if (aEvent.FeatureURL.Path == "AssignLayout" || + aEvent.FeatureURL.Path == "StatusSelectionMode" || + aEvent.FeatureURL.Path == "Signature" || + aEvent.FeatureURL.Path == "SelectionMode" || + aEvent.FeatureURL.Path == "StatusBarFunc" || + aEvent.FeatureURL.Path == "FreezePanesColumn" || + aEvent.FeatureURL.Path == "FreezePanesRow") + { + sal_Int32 aInt32; + + if (aEvent.IsEnabled && (aEvent.State >>= aInt32)) + { + aBuffer.append(aInt32); + } + } + else if (aEvent.FeatureURL.Path == "TransformPosX" || + aEvent.FeatureURL.Path == "TransformPosY" || + aEvent.FeatureURL.Path == "TransformWidth" || + aEvent.FeatureURL.Path == "TransformHeight") + { + const SfxViewShell* pViewShell = SfxViewShell::Current(); + if (aEvent.IsEnabled && pViewShell && pViewShell->isLOKMobilePhone()) + { + boost::property_tree::ptree aTree; + boost::property_tree::ptree aState; + OUString aStr(aEvent.FeatureURL.Complete); + + aTree.put("commandName", aStr.toUtf8().getStr()); + pViewFrame->GetBindings().QueryControlState(nSID, aState); + aTree.add_child("state", aState); + + aBuffer.setLength(0); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + aBuffer.appendAscii(aStream.str().c_str()); + } + } + else if (aEvent.FeatureURL.Path == "StatusDocPos" || + aEvent.FeatureURL.Path == "RowColSelCount" || + aEvent.FeatureURL.Path == "StatusPageStyle" || + aEvent.FeatureURL.Path == "StateTableCell" || + aEvent.FeatureURL.Path == "StateWordCount" || + aEvent.FeatureURL.Path == "PageStyleName" || + aEvent.FeatureURL.Path == "PageStatus" || + aEvent.FeatureURL.Path == "LayoutStatus" || + aEvent.FeatureURL.Path == "Scale" || + aEvent.FeatureURL.Path == "Context") + { + OUString aString; + + if (aEvent.IsEnabled && (aEvent.State >>= aString)) + { + aBuffer.append(aString); + } + } + else if (aEvent.FeatureURL.Path == "InsertMode" || + aEvent.FeatureURL.Path == "WrapText" || + aEvent.FeatureURL.Path == "NumberFormatCurrency" || + aEvent.FeatureURL.Path == "NumberFormatPercent" || + aEvent.FeatureURL.Path == "NumberFormatDecimal" || + aEvent.FeatureURL.Path == "NumberFormatDate" || + aEvent.FeatureURL.Path == "ShowResolvedAnnotations") + { + bool aBool; + + if (aEvent.IsEnabled && (aEvent.State >>= aBool)) + { + aBuffer.append(OUString::boolean(aBool)); + } + } + else if (aEvent.FeatureURL.Path == "ToggleMergeCells") + { + bool aBool; + + if (aEvent.IsEnabled && (aEvent.State >>= aBool)) + { + aBuffer.append(OUString::boolean(aBool)); + } + else + { + aBuffer.append("disabled"); + } + } + else if (aEvent.FeatureURL.Path == "Position") + { + css::awt::Point aPoint; + + if (aEvent.IsEnabled && (aEvent.State >>= aPoint)) + { + aBuffer.append( OUString::number(aPoint.X) + " / " + OUString::number(aPoint.Y)); + } + } + else if (aEvent.FeatureURL.Path == "Size") + { + css::awt::Size aSize; + + if (aEvent.IsEnabled && (aEvent.State >>= aSize)) + { + aBuffer.append( OUString::number(aSize.Width) + " x " + OUString::number(aSize.Height) ); + } + } + else if (aEvent.FeatureURL.Path == "LanguageStatus" || + aEvent.FeatureURL.Path == "StatePageNumber") + { + css::uno::Sequence< OUString > aSeq; + + if (aEvent.IsEnabled) + { + OUString sValue; + if (aEvent.State >>= sValue) + { + aBuffer.append(sValue); + } + else if (aEvent.State >>= aSeq) + { + aBuffer.append(aSeq[0]); + } + } + } + else if (aEvent.FeatureURL.Path == "InsertPageHeader" || + aEvent.FeatureURL.Path == "InsertPageFooter") + { + if (aEvent.IsEnabled) + { + css::uno::Sequence< OUString > aSeq; + if (aEvent.State >>= aSeq) + { + aBuffer.append(u'{'); + for (sal_Int32 itSeq = 0; itSeq < aSeq.getLength(); itSeq++) + { + aBuffer.append("\"" + aSeq[itSeq]); + if (itSeq != aSeq.getLength() - 1) + aBuffer.append("\":true,"); + else + aBuffer.append("\":true"); + } + aBuffer.append(u'}'); + } + } + } + else if (aEvent.FeatureURL.Path == "TableColumWidth" || + aEvent.FeatureURL.Path == "TableRowHeight") + { + sal_Int32 nValue; + if (aEvent.State >>= nValue) + { + float nScaleValue = 1000.0; + nValue *= nScaleValue; + sal_Int32 nConvertedValue = o3tl::convert(nValue, o3tl::Length::twip, o3tl::Length::in); + aBuffer.append(nConvertedValue / nScaleValue); + } + } + else + { + // Try to send JSON state version + SfxLokHelper::sendUnoStatus(pViewFrame->GetViewShell(), pState); + + return; + } + + OUString payload = aBuffer.makeStringAndClear(); + if (const SfxViewShell* pViewShell = pViewFrame->GetViewShell()) + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, payload.toUtf8().getStr()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |