summaryrefslogtreecommitdiffstats
path: root/sfx2/source/control
diff options
context:
space:
mode:
Diffstat (limited to 'sfx2/source/control')
-rw-r--r--sfx2/source/control/bindings.cxx1783
-rw-r--r--sfx2/source/control/charmapcontrol.cxx222
-rw-r--r--sfx2/source/control/charwin.cxx263
-rw-r--r--sfx2/source/control/ctrlitem.cxx344
-rw-r--r--sfx2/source/control/dispatch.cxx2076
-rw-r--r--sfx2/source/control/emojicontrol.cxx156
-rw-r--r--sfx2/source/control/emojipopup.cxx73
-rw-r--r--sfx2/source/control/emojiview.cxx224
-rw-r--r--sfx2/source/control/emojiviewitem.cxx84
-rw-r--r--sfx2/source/control/itemdel.cxx77
-rw-r--r--sfx2/source/control/listview.cxx443
-rw-r--r--sfx2/source/control/minfitem.cxx75
-rw-r--r--sfx2/source/control/msg.cxx56
-rw-r--r--sfx2/source/control/msgpool.cxx326
-rw-r--r--sfx2/source/control/objface.cxx443
-rw-r--r--sfx2/source/control/recentdocsview.cxx314
-rw-r--r--sfx2/source/control/recentdocsviewitem.cxx348
-rw-r--r--sfx2/source/control/recentdocsviewitem.hxx67
-rw-r--r--sfx2/source/control/request.cxx763
-rw-r--r--sfx2/source/control/sfxstatuslistener.cxx219
-rw-r--r--sfx2/source/control/shell.cxx743
-rw-r--r--sfx2/source/control/sorgitm.cxx85
-rw-r--r--sfx2/source/control/statcach.cxx503
-rw-r--r--sfx2/source/control/templatecontaineritem.cxx20
-rw-r--r--sfx2/source/control/templatedefaultview.cxx82
-rw-r--r--sfx2/source/control/templatedlglocalview.cxx416
-rw-r--r--sfx2/source/control/templatelocalview.cxx945
-rw-r--r--sfx2/source/control/templateviewitem.cxx124
-rw-r--r--sfx2/source/control/thumbnailview.cxx1220
-rw-r--r--sfx2/source/control/thumbnailviewacc.cxx874
-rw-r--r--sfx2/source/control/thumbnailviewacc.hxx215
-rw-r--r--sfx2/source/control/thumbnailviewitem.cxx314
-rw-r--r--sfx2/source/control/unoctitm.cxx1285
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: */